 All right, I'm Bob Kukora. I work at Cisco in the neural group. I'm Ivar Lazaro. I work in Cisco in the neural group and we also have a special guest actually. We have the group-based policy PTL submit there that now is forced to help us. And quite a few other group-based policy collaborators. All right. So agenda here is really to try to get people familiar with group-based policy hands-on. You know, you may already have seen presentations at this summit or previous summits that present the models, talk about, you know, all kinds of things. This is really kind of, okay, I want to try it out but I don't know where to start. We're trying to give you just enough experience to kind of get things running and find your way around a little bit. I don't know if we'll be able to get through everything we intend or maybe we'll get through that in half an hour and talk for the rest of the time or whatever. But we're going to start out with just a very quick overview of GBP and talk about kind of what it does and what you need to know kind of to get through the lab. Then we've already got a head start on the second step really of distributing and launching the VM image. The third step is deploying OpenStack with GBP included. So we've got a DevStack branch that has things configured for that. If you've done it already, it's probably fine. If not, we'll do that. While that DevStack is running, we'll talk a little bit about the basic model of GBP. So if you're not already familiar with that, you'll know what we're doing. And then the first exercise is very much a step-by-step, very simple scenario. We'll walk you through kind of everything you need to do. Second exercise is a little more complicated app kind of using that same knowledge where we'll kind of let you work your way through it using what you've learned. We're here to help as well. Then we'll talk a little bit about external connectivity. Given the variety of different machines and stuff, we're not really expecting to get that working. Just networking setup for that can be a trick under VMs. We'll talk about service chaining and then do an exercise related to that. And then, depending on how far we get, there might be homework. So I see that there are new people coming. So welcome to a group-based policy lab. And if you guys have some free USB stick, you can pass them down for the installation. So many of you already copied the files, but let's go through it again for the sake of record. So you will be provided a USB stick. You will find a GEP hands-on folder there. So just copy that on your local system. And you will find an OVA in this folder that you can use with virtual box or VMware Fusion or with whatever else I provide you that can support that. There is also one special USB stick around which has a QCao 2 disk that we can pass around for KVM. So if you need that, just let me know. I will try to find that and give it to you. So to get ready with the system, we would like you to install the VM, to go on the DevStack directory on the home. So you will see Login is GPP and password is GPP for logging in and run DevStack with the stacksh script. So once we are all more or less at that step, it will take around five minutes to be ready. So we will go through the GPP model and we'll give some explanation. So I would like to know how many of you are not at the stack step yet? So you've got to get there and a couple more. Okay. As I mentioned, if you are using Fusion and I think also with KVM, you probably want to comment out the lines that are setting IP addresses to the local host address. I guess VirtualBox does some port forwarding and redirection stuff to basically let the local host of the VM be accessible as local host on the host. Yes. The other hypervisors don't tend to do that by default. So how many of you are already running stack? So it's up and running, right? So is there any question at this point, someone that's not able to install it? At the bottom, you'll see. Yeah, yeah. So you will see that's done because at the end, it's going to say stack is done. Yes, one thing to look for there. There'll be a URL for connecting to it from horizon. Yes. And you know, you want to make sure that you can connect horizon from that URL and things are up. So the SSH instructions are these. You're using VirtualBox, right? So you're going to use SSH minus P2224, GBP at local host. And it doesn't work? What can go wrong? Say that again, please. Yeah, what system are you running? I don't know if I can edit this here. Can we edit this and add that? Yeah, I'm trying to. How do we get this into edit mode? Oh, no, we're going to exit here. It's going to come back. It'll be right back in a moment with that. Well, he's doing that. If you're using, if you're not using VirtualBox, you'll want to log in to the console of your VM, do IP adder or if you figure whatever, get the IP address, and then you should be able to SSH to that, just on the normal SSH part. Yeah, when DevStack runs, it shows the URL for connecting to Ryzen. I've been ready for at least an hour. Anybody running into difficulties need a hand? All right. So how many of you still have DevStack running? How many are done with DevStack? And how many haven't launched DevStack yet? That's okay. Okay. Let me talk to the model for a little bit here while we're getting everybody caught up. Does that make sense? Actually, the model. Yeah. The order switched around a little bit here, but that's okay. All right. So it's just quick introduction to group-based policy. If you're not familiar with it, it's currently a set of Stackforge projects, and we're working towards inclusion in OpenStack as part of the Big Tent effort. We've had some review of that and some feedback, and, you know, we're taking that to heart and working towards that goal. Currently made up of four projects. There's the service itself, the GBP service, which we'll talk about a little bit more. There's the Python client, which also has a command line interface, just like pretty much any OpenStack project. There are also add-ons to heat and to horizon. So, you know, we're trying to cover the full user experience and integrating with those. Currently, the development of group-based policy has been trailing OpenStack release cycles by, you know, month or two, I don't know, three. So we do have a Juno release that's kind of complete and has a stable branch and so forth, and has been packaged in various distributions. We're still working on the Kilo version. We should have a very usable Kilo milestone release that can get packaged for distributions very soon. What we're working with today is kind of, you know, fairly current stuff, but I think it's on a branch. It's Master Branch. Oh, it's Master. Okay. And, you know, as soon as possible, the master will then start tracking liberty and will have a Kilo stable branch as well. The goal of group-based policy is to provide an intent-driven API currently for Neutron, and basically this automates and hides a lot of low-level networking details that application developers and deployers and so forth generally don't need to worry about. So things like creating the networks, subnets, routers, security groups, configuring services and so forth. You can use all this stuff, but you don't really need to know the details to use it through group-based policy. So basically the group-based policy API is a set of restful resources, you know, just like you're used to, that are intended to capture the intent. We'll see the model in a few minutes that introduced those resources. Basically, we're talking about connectivity between groups of application components. So groups is a key concept. Within those groups you have policy targets, we call them currently, that basically have Neutron ports. So generally it's VMs or the members of those groups and their network ports are the policy targets that we're talking about in those groups. So in addition to just controlling connectivity, you know, allow and deny, we're also able to redirect traffic through high-level services such as load balancing, firewalls, anything else that you need. We also try to separate the concerns a little bit between the people developing an application, the people kind of managing the deployment of an application and maybe controlling, you know, responding to security events, things like that, and the person administering the cloud and kind of providing sets of services and things that are available. Did they cover all this? This? Oh, if you're... Don't forget the branch there. Let me know when you're ready. On the next slide, the next couple lines we're talking about drivers. So basically group-based policy architecture is built with drivers. There's a resource mapping driver that implements everything in group-based policy purely in terms of standard Neutron APIs and the resources there. There's also the possibility to develop other drivers that are optimized to work with back-end systems that know something about intent, which is definitely an upcoming approach and I think Open Daylight and other projects are working there. So this is intended to be a front-end for all those sorts of systems, as well as regular Neutron. So you don't need anything special. It works with OpenVswitch, LinuxBridge, any back-ends. And currently the service part of it where the rest resources are implemented is within the Neutron server. We're working to, during the Liberty Cycle, to separate that out as a separate service, separate service endpoint that would be advertised in Keystone and eventually evolving to where this grouping and policy concepts and intent-based APIs can span more than just Neutron, so compute storage and other things could fall into that model. So it's very actively being developed. This is the kind of thing we're really looking for people interested in this to get evolved in, particularly people coming from some of those other, if your focus is NOVA, not Neutron, but you are interested in intent, would love to get you involved. The basic model, let's see, we can start with kind of the, there's a, these are, I guess all the black, they're not all black, but most of the names on here are the names of rest resources. L3 policy is kind of a overlapping, non-overlapping address space, so subnets and so forth get allocated within that, they're rootable within that, and tenants can create one or more of those. They get created for you by default, so a lot of times you don't even see them if you just need one. The L2 policies down here, basically the policy groups that you see are contained in L2 policies, normally one will get created for each of those, but if you want to share them you can. That controls your broadcast domain and things like that. A lot of times applications don't need to worry about, but when they do, they do have the ability to control that. So policy targets are the items that we are grouping, so right now policy target basically owns and references a neutron port, so VMs that use policy targets from the same group are part of the same group of VMs. A policy tool set, generally often also referred to as a contract, we may rework some of the naming as we go here to bring things in sync between various open source projects that are all working in this space. Basically you have format you see below where classifiers are going to match certain kinds of traffic, so based on port numbers, you know, protocol types, those sorts of things, and then an action. Action could be allow, it could be, the default is denied, so you don't have to worry about I don't know, can you explicitly deny? I guess not. It's a white list. Yeah, it's a white list. But in addition to accept, you can basically redirect through a service chain, and that's kind of the model for doing things like load balancing and so forth, and incorporating that into the intent of that into the model without having to get into all this sort of imperative approach to building up load balancers and using APIs and so forth. So the relationships here between the policy rule set and the groups is that the groups consume any number of rule sets and provide any number of rule sets. Do I miss anything? Any questions on this? Is this sort of enough to work with so you can understand what we're doing? They will have many more questions by trying it, I assure you. Okay. Okay, so if I'm the database tier in a three tiered app, I'm providing a rule set that describes access to the database. If I'm the app tier, I'm consuming that. So basically, now we are going through the first exercise, right? And here we will work a lot with groups and policy rule sets. So this is going to be a very basic session. For this first exercise, we will work through and I'm going to show you what to do in the API in order to get a very simple scenario. And then we'll go to a more complicated thing. Now, the most important thing you need to know here is the concept of group and policy rule sets. Group are these, as Bob said, are a homogeneous collection of policy targets that they all behave the same way and how they behave is defined by the policy rule set. So to get back to your question, when you provide a policy rule set it's like saying, this is the interface you can use in order to connect with me. Interface in the sense of a contract, right? So you can like say, this is what I expose, whatever wants to consume it can. And then if the underlying infrastructure allows connectivity and the policy rule set is defined correctly for this traffic to happen then you will be able to consume it and finally consume your workload, basically. So how are we with the installation? Is that done? Most of you, all of you, is there anyone with... I see new guys coming, if you want to grab a USB stick you will have to trail a little bit but there will be people giving you instructions. So let's connect to Horizon. You can reach Horizon in port 8090 so let me know if, from your local browser, if you're using Vintual Box, if you're using VMware Fusion you can directly use the IP address of your VM to reach it. Let me know if there is any issue, reaching Horizon, are we set? Yeah, the opposite login. So the login is admin and the password is ABC123. Which is missing from the slides, of course. So admin ABC123. No, there is not whiteboard. Yeah, I'll show you at the end of the script. Yeah, there is a way to SSH. It's in this slide as well. Let me just add very quickly. Yeah, somebody suggested that. Some people run into issues and that might help. But if you didn't do it and it's working, don't worry. If you're SSHD and you're okay. Ah, that might be what we need. Admin and ABC123. These slides are posted anywhere right now. We're doing okay? That's great of you. Yeah, let's have a header pad. You can access this header pad, group-based policy, hands-on. Admin, ABC123. Yeah, then I'll try to put in the link to the slides as well. This is unrelated. So how many of you were able to log into Horizon? Okay, perfect. So let's wait a little bit more. So the objective for this first exercise is to have two very simple groups and being able to ping and SSH VMs from one group to another. So we're going through a very basic setup and then we will complicate it a little bit based on what you learned and you will be left alone playing with GBP. Not alone. Not literally. We are a group. The URL, the header pad URL. It's group-based policy, hands-on. The URL. Here you go. There's a link to the slides there as well now. It's even more readable. Let's set up for everybody to read. Are we ready to move on? Can we start? Okay. So on Horizon, you will see a policy tab on the left. So under project, you're going to see a policy tab. So go there and go in the application policy sub-tab. So the first thing we're going to do is we're going to create a policy rule set as a first step for this interaction between groups to happen. So again, the policy rule sets define the set of policy and the interaction between groups. A group will be a provider of this rule set and other groups will consume this rule set so that they can consume the workload they offer basically. So let's go here on the create page and create policy rule set. Decide the name, for instance, test. And then do you find this? It's all right. And you'll go next and you can decide a set of rules that you need to participate in this policy rule set. Now, there are some rules here that have been pre-created by DevStack for the sake of making this more simple. But ideally, there is a separation of concern in this model for which the final user of the system will actually find even policy rule sets sometimes. Already created and maybe even shared by an admin. Can you create this policy rule set for now? No, it's fine. So you can add the ICMP by direction allow rule for now and then we will go into the detail of rules later. And then you can click create. So with this in the application policy tab, you will see your policy rule set test that has been created. Now go in the group tab and we can finally mess a little bit with the main object of this model, which are groups. So you can already create a group. You can call it, for instance, provider. Let's create the provider group. And this provider in the next tab can provide the test policy rule set. So let's get to this point. I don't know how many of you are already ahead or behind. Where are you? Okay, so the policy rule set is created? Okay. So you go in the tab groups, which is right up the application policy. And then you can click create or add group for creating a new group. Is the group providing the application policy? Okay. Now, how are we with the group creation, the first group provider? Are we done? Okay. So the provided policy rule set has to be tested in this case. Or you can update it later, of course. But one step. This policy rule set is the one you just created and it's providing ICMP for now. It's allowing ICMP, actually. Let me make it stuck and needs help. Yeah. You can just go on with the defaults for now. So the only important thing is that you provide test and then you can go next and finally create. Okay. Test, yeah. So it's got the ICMP. That's okay. Right. I think you did that with the group. Wait, wait, wait. We need to update the quality rule set. ICMP. So what are we creating a policy rule set now, right? The first group we are creating is provider, yeah. It's called provider. Did you create a policy rule set? Yeah, test. Is this correct? Yeah, I think so. Okay. Yeah, those are the ones that are pre-existing. That's the rule set. I'm sorry? That's the one I happen to create, I think. Okay. Then we go back to groups. Which one? I don't know. What did the instructions say? No, this is a different step. Yeah, provider must have been it. So when you hit next, you will find the network policy tab. You just click create from there. So you're going to provide test, but not consume test, I guess. Yeah. So I think you highlight that and then if you have a plus, does it... You don't want to consume it. You want to provide it here, right? So I think you may have a plus over here. Hey. Maybe that's for creating a new one. No, no, the consume can be empty. So we are creating a group which provides command lines. Yeah, I guess it's... It should be okay. You can change them later if it's not right. Yeah, don't worry about that. Just use the defaults. Create. Let me see what happened. Okay. So this... We're providing and consuming. So we can technically go into here. Are we fine here? Yeah, I think I'll find... He was just interested in taking some pictures. Okay. And then I consumed this group and walked through and made this move. Provide a perfect... All right. So now you... I think you can just kind of reflect and wipe it out. Maybe... In the group? I'm not that familiar with the private interface here. They won't write anything. Yeah? So in the newly created group maybe if you click in the newly created group so from the group tab you can click on the name on the group you just created and you will find a button which says create member. A member of a group is in this case basically a virtual machine which is attached to this group. As you will see you don't need to specify anything networking-wise. You just create a member of the group. You can specify your flavor of course and you can specify the instance name and the image but you don't need to specify anything networking related. You just hit create and this policy target will be created on the provider group. So on the group tab you will see the group you just created. You can click on the name of this group and then on the top right there's going to be an add member. So the creation of the member is the same as creating a normal virtual machine in this case but just with this few information. A viability zone should be just Nova or did you change it? Nova viability zone found? Could you give a look please? This is actually going through Nova. This is just a way for the UI to be a bit more group-centric basically. So we are driving the workflow through the group but you are still able to go through the computer if you want and do the creation normally. It is just requires one more step because when you create the policy target on the group and you get the neutron port from it, you have to do the creation specifying the neutron port to Nova and this is not a viable from Horizon today. Go ahead. So a definition of group is a collection of ports basically so the VM incidentally is attached to the port but it's a collection of ports. Yes as is today the policy target group is a collection of policy targets and in the reference implementation we are showing today the policy target is directly mapped to a neutron port. So it's using neutron resources underneath and the policy target has a one-on-one relationship with the neutron port today. The same port can't no. The same port today can't be memorable. This is so are you talking about what the model can offer or the current implementation? The current implementation with the resource mapping driver is basically placing this VM in a given subnet so your group will be somehow mapped to one or more subnets. The user don't have to go into this detail so the user is not going to see it because it's not probably going to be useful for him. This is where the separation of concerns is which means that if you change the underling implementation and you want to do something completely different as far as connectivity is concerned you will still be able. However this will require a group-based policy driver to be created for that. Even with the standard resource mapping driver you have a lot of flexibility there's an L2 policy resource and you can pass one of those in when you do a group create and you can pass the same one in to several different groups and therefore cause them to have the same beyond the same L2 network. And if you don't do that, if you don't pass one in then a new L2 policy and therefore our neutron network gets created for each individual group. Exactly. Everything is orchestrated here. The only thing you care about are groups and what policy are applied to those groups but you don't care about networking in this case at all. This is actually possible of course through the group-based policy model but this is done at a different layer of concern. So there is an operator concern which we are not exposing right now for this lab because we want to go through very simple steps but there will be definitely in the future more advanced labs in which we show all these operator concerns and how you can actually specify more infrastructure related policies. Moreover all the stuff that is happening today right now by in the sense of the subnet that are created, the network that are created or even the routers and so forth are not just pre-created in this model but there is actually a mechanism included in group-based policy that will take some default configuration that your cloud admin for instance will have done for its tenant and so every time you create a group, if this group is missing the floor and under its feet basically this floor will be created by this implicit driver which will do all the infrastructure creation based on the operator configuration. So that's how that L2 policy was getting defaulted if you didn't specify it and similarly to create an L2 policy it has to belong to an L3 policy which defines the IP address space and how that would get split up into subnets and things like that. That basically gets created per user by default so if that user has never created one and doesn't pass one in the default one gets created and you know you can look around at the neutron resources in Horizon there and see the things that have been created. So just let's go a bit faster here. Is the member created? Perfect. Yes. Exactly. The policy target has an important... Yes. This is actually a very good feedback. So what is happening here is UI orchestration. So what you will do from the REST API perspective is that you will create a policy target and this policy target may be associated to a port if you want or this port may be created automatically based on the infrastructure that is underneath and then this policy target could be used for creating a VM but it's actually a good point. This step is actually two in one so this is simply UI orchestration in which your member creation is defined as a creation of a policy target in which a VM is planned. So potentially you could even have, using command line, you could use Nova to boot an instance that has two nicks and each of those nicks has a different policy target in different policy target groups. Definitely. So the full richness of what you can do is available. Just the user interface kind of makes the easy stuff easy. Yeah. Yeah, that's a bug in the UI. Damn it. I knew someone would do that. No, so you just use one, number one. It's a problem in our UI implementation today which is not working. But you could do that from Nova normally so if you had to do that from the CLI it would definitely work. Now do that again without pictures and this time the group you create, so let's start from creating the group not from creating the policy rule set. The policy rule set is fine. But this time the group you create got to consume the policy rule set and not create. Everything else is basically the same. Yeah, yeah, you can launch. Yeah, definitely. Create the member and launch it. Yes? Good question. So here we are going through a very basic topology in which we want to showcase two member of two groups being able to ICMP one another without any kind of explicit networking involved. So this is a very simple configuration. Actually the idea here is to get you familiar a little bit with the UI. We will go on something more interesting later after this. I'm sorry. Sorry, say it again. You want to see traffic, basically? Sure, of course. What can go wrong? I'm sorry. No, the policy rule set test is fine. Don't create a new one. What you got to do is create a new group and this group will this time consume the policy rule set. Okay, so this is wrong, right? Because this is providing again. Right, so if I edit it, maybe I'm just doing test work for you. How do I get rid of this? So for this you got to go on the exit this one. So unfortunately you got to go on the consumer. And so you got to provide and remove this. So you got to remove policy rule set and you remove the provider here. You have to select it. Okay, and then save. And then maybe you got to update. And then you add consumer policy rule set. Yes, so you got to consume. Select, you got to select. Yes, and then save changes. Yeah, the UI may use some work. Yes? Okay, thanks for the feedback. Okay, that's a good feedback. Thanks. So today the policy rule set you are using and it's only having a simple allow rule in it for ICMP. What you got to keep in mind which is very important is that at some point you may change your policy dynamically and for instance you may add a new rule to this policy rule set which says redirect to a firewall and load balancer for instance. So you can have a full service chain defined with our service chain API and we will go through it if we have time today. Even though it's not really basic but we can go through it. So the usability of the model and the fact that you have no concern may make your operation, your operator add constraints to your policy rule sets or the user itself adding more rule to these rule sets. Which can basically change almost dramatically your application by just doing a couple of clicks and changing the rule. Exactly. Yes, yes, please do. Also, of course, if you want to have a more fine grain control of the model for any single exception, any single server you want to modify something in the port and so forth you still have neutron there. So you can still go there, you can still use neutron and it's going to be. So in this case keep in mind that the whole address space you have as a user is independent from the address space of all the other tenants and may overlap. So if you want you can have even the whole private address space even just for you at a layer 3 policy level that was explained in the first slides. So if you really have some specific networking needs, which is probably not the case if you are using group-based policy, if you are using it, you are just a user and you want groups to communicate with a given policy. You don't really make care about it. But if you do there are operator level kind of APIs that allows you to create different address spaces and use them. Or even single subnets to neutron that can be associated to groups. So there is a lot of variety. One additional point here is it's really the group-based policy that's managing kind of how things like subnets and firewall security groups, all that kind of thing are used. For instance right now if you look into detail here you'll see that for each group that you've created there's a small subnet allocated. What will happen is the ID instances, those will have IP addresses in that subnet and if you exceed the size of the subnet a new subnet will get allocated that's also part of that group. And meanwhile all the security group rules that reference these things are matching those subnets. So when that additional subnet gets added new rules get added to existing stuff. So this is all this kind of orchestration that this is doing for you that if you were just using neutron it would be extremely complicated. Yes. Exactly. That's all open stack underneath. This is the final state you should get by the way. I'm sorry? So how did that end up with the same L2 policy? Let me shut this off. So the L2 policy it's basically what the groups are attached in the sense of a broadcast domain which you may not care like in this case which is everything done automatically and all your groups are isolated from this point of view. And as a matter of fact you didn't do it. But if you wanted to, if your operator wanted to provide you some default way of connecting your network then he could do it and the L2 policy so it's like the broadcast domain for the groups. And then there is a L3 policy which is the address space. I'm coming to you don't worry. I'm sorry? The L2 policy run? Yes. We had a question there? There is no way to change it. As a matter of fact we are going to do it now because we say that the objective of these rules is to have two VMs that could ping and SSH each other but we just used an ICMP rule. So what we can show you now is actually how to change your policy rule set in order to allow this extra operation. So I don't know where you are in terms of groups and providing and consuming the policy rule sets. This is what you should see. So how many of you are there? Okay, cool. So let's go now and actually update your policy rule set. So we forgot to add the SSH rule so we can go on application policy and edit the test policy rule set you created and you can actually at this point specify both the ICMP and SSH by directional allow rules and finally update the policy rule set. If you want to test some traffic maybe we may skip this for now but if you want to you can go on the VM console and from the VM console you can try that you can actually ping the VMs and SSH in them after you do this. So this is for homework. Any questions so far? One member to multiple groups is not supported today. Yes. So the groups cannot overlap as for today. There are plans around policy tags that may kind of bring this kind of behavior you are looking for or even overlapping groups but it's really under discussion for liberty. So don't use it for policy rule sets because it's not supported by the current implementation because of some issues that are in Neutron that we are looking for together with the Neutron team. And so what basically does it gives the operator a way to create some basic structure that can be used by all the tenants so the same way in Neutron you can share networks. Here you can share almost everything. So you can create shared rules. You can create shared classifiers, shared actions or even policy rule sets so that when a new tenant comes in your cloud they will just see okay I see some policy rule set and I know what they mean so they are like web access or database access or whatever so I can very easily create my 3TR hub for instance by just using what the admin already provided me. In order to do that instead of having the admin every time creating those objects we can share them and they are scoped correctly so that having multiple tenants using them don't actually affect each other. So if I have for instance if I have my tenant with a specific group consuming a web policy rule set it doesn't mean, it may be but it doesn't mean it has connectivity with for instance another tenant which is actually providing it so the sharing capability will scope you around your tenant for easy resolution. There was a... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...