From: Ben Pfaff Date: Thu, 11 Feb 2010 19:11:23 +0000 (-0800) Subject: Merge "master" into "next". X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c69ee87c10818267f991236201150b1fa51ae519;p=openvswitch Merge "master" into "next". The main change here is the need to update all of the uses of UNUSED in the next branch to OVS_UNUSED as it is now spelled on "master". --- c69ee87c10818267f991236201150b1fa51ae519 diff --cc datapath/datapath.c index 46bb6961,9f415ac5..1e878ebe --- a/datapath/datapath.c +++ b/datapath/datapath.c @@@ -420,10 -420,9 +420,10 @@@ got_port_no if (err) goto out_put; + set_dp_devs_mtu(dp, dev); dp_sysfs_add_if(dp->ports[port_no]); - err = __put_user(port_no, &port.port); + err = __put_user(port_no, &portp->port); out_put: dev_put(dev); diff --cc lib/command-line.c index 2f997984,6b79c5ea..17344c36 --- a/lib/command-line.c +++ b/lib/command-line.c @@@ -47,150 -47,3 +47,150 @@@ long_options_to_short_options(const str return xstrdup(short_options); } +/* Runs the command designated by argv[0] within the command table specified by + * 'commands', which must be terminated by a command whose 'name' member is a + * null pointer. + * + * Command-line options should be stripped off, so that a typical invocation + * looks like "run_command(argc - optind, argv + optind, my_commands);". */ +void +run_command(int argc, char *argv[], const struct command commands[]) +{ + const struct command *p; + + if (argc < 1) { + ovs_fatal(0, "missing command name; use --help for help"); + } + + for (p = commands; p->name != NULL; p++) { + if (!strcmp(p->name, argv[0])) { + int n_arg = argc - 1; + if (n_arg < p->min_args) { + ovs_fatal(0, "'%s' command requires at least %d arguments", + p->name, p->min_args); + } else if (n_arg > p->max_args) { + ovs_fatal(0, "'%s' command takes at most %d arguments", + p->name, p->max_args); + } else { + p->handler(argc, argv); + if (ferror(stdout)) { + ovs_fatal(0, "write to stdout failed"); + } + if (ferror(stderr)) { + ovs_fatal(0, "write to stderr failed"); + } + return; + } + } + } + + ovs_fatal(0, "unknown command '%s'; use --help for help", argv[0]); +} + +/* Process title. */ + +#ifdef __linux__ +static char *argv_start; /* Start of command-line arguments in memory. */ +static size_t argv_size; /* Number of bytes of command-line arguments. */ +static char *saved_proctitle; /* Saved command-line arguments. */ + +/* Prepares the process so that proctitle_set() can later succeed. + * + * This modifies the argv[] array so that it no longer points into the memory + * that it originally does. Later, proctitle_set() might overwrite that + * memory. That means that this function should be called before anything else + * that accesses the process's argv[] array. Ideally, it should be called + * before anything else, period, at the very beginning of program + * execution. */ +void +proctitle_init(int argc, char **argv) +{ + int i; + + if (!argc || !argv[0]) { + /* This situation should never occur, but... */ + return; + } + + /* Specialized version of first loop iteration below. */ + argv_start = argv[0]; + argv_size = strlen(argv[0]) + 1; + argv[0] = xstrdup(argv[0]); + + for (i = 1; i < argc; i++) { + size_t size = strlen(argv[i]) + 1; + + /* Add (argv[i], strlen(argv[i])+1) to (argv_start, argv_size). */ + if (argv[i] + size == argv_start) { + /* Arguments grow downward in memory. */ + argv_start -= size; + argv_size += size; + } else if (argv[i] == argv_start + argv_size) { + /* Arguments grow upward in memory. */ + argv_size += size; + } else { + /* Arguments not contiguous. (Is this really Linux?) */ + } + + /* Copy out the old argument so we can reuse the space. */ + argv[i] = xstrdup(argv[i]); + } +} + +/* Changes the name of the process, as shown by "ps", to 'format', which is + * formatted as if by printf(). */ +void +proctitle_set(const char *format, ...) +{ + va_list args; + int n; + + if (!argv_start || argv_size < 8) { + return; + } + + if (!saved_proctitle) { + saved_proctitle = xmemdup(argv_start, argv_size); + } + + va_start(args, format); + n = vsnprintf(argv_start, argv_size, format, args); + if (n >= argv_size) { + /* The name is too long, so add an ellipsis at the end. */ + strcpy(&argv_start[argv_size - 4], "..."); + } else { + /* Fill the extra space with null bytes, so that trailing bytes don't + * show up in the command line. */ + memset(&argv_start[n], '\0', argv_size - n); + } + va_end(args); +} + +/* Restores the process's original command line, as seen by "ps". */ +void +proctitle_restore(void) +{ + if (saved_proctitle) { + memcpy(argv_start, saved_proctitle, argv_size); + free(saved_proctitle); + saved_proctitle = NULL; + } +} +#else /* !__linux__ */ +/* Stubs that don't do anything on non-Linux systems. */ + +void - proctitle_init(int argc UNUSED, char **argv UNUSED) ++proctitle_init(int argc OVS_UNUSED, char **argv OVS_UNUSED) +{ +} + +void - proctitle_set(const char *format UNUSED, ...) ++proctitle_set(const char *format OVS_UNUSED, ...) +{ +} + +void +proctitle_restore(void) +{ +} +#endif /* !__linux__ */ diff --cc lib/coverage.c index 8b5e9d9c,92bc2b89..5c99c18a --- a/lib/coverage.c +++ b/lib/coverage.c @@@ -30,8 -30,7 +30,8 @@@ static unsigned int epoch; static void - coverage_unixctl_log(struct unixctl_conn *conn, const char *args UNUSED, - void *aux UNUSED) -coverage_unixctl_log(struct unixctl_conn *conn, const char *args OVS_UNUSED) ++coverage_unixctl_log(struct unixctl_conn *conn, const char *args OVS_UNUSED, ++ void *aux OVS_UNUSED) { coverage_log(VLL_WARN, false); unixctl_command_reply(conn, 200, NULL); @@@ -109,7 -108,7 +109,7 @@@ coverage_hit(uint32_t hash unsigned int word_mask = 1u << (bit_index % BITS_PER_WORD); if (hit[word_index] & word_mask) { -- return true; ++ return true; } else { hit[word_index] |= word_mask; return false; diff --cc lib/dpif-linux.c index ddf9298e,3920d462..4c343256 --- a/lib/dpif-linux.c +++ b/lib/dpif-linux.c @@@ -107,7 -107,7 +107,7 @@@ dpif_linux_enumerate(struct svec *all_d } static int - dpif_linux_open(const char *name, const char *type UNUSED, bool create, -dpif_linux_open(const char *name OVS_UNUSED, char *suffix, bool create, ++dpif_linux_open(const char *name, const char *type OVS_UNUSED, bool create, struct dpif **dpifp) { int minor; diff --cc lib/dpif-netdev.c index 042837c1,e9f006c3..1dee14b4 --- a/lib/dpif-netdev.c +++ b/lib/dpif-netdev.c @@@ -245,7 -245,7 +245,7 @@@ create_dp_netdev(const char *name, int } static int - dpif_netdev_open(const char *name, const char *type UNUSED, bool create, -dpif_netdev_open(const char *name OVS_UNUSED, char *suffix, bool create, ++dpif_netdev_open(const char *name, const char *type OVS_UNUSED, bool create, struct dpif **dpifp) { if (create) { diff --cc lib/fatal-signal.c index 60a188e6,9aa50e42..80ecfc35 --- a/lib/fatal-signal.c +++ b/lib/fatal-signal.c @@@ -239,13 -232,12 +239,13 @@@ unlink_files(void *aux OVS_UNUSED do_unlink_files(); } -/* This is a fatal_signal_add_hook() callback (via unlink_files()). It will be - * invoked from an asynchronous signal handler, so it cannot call most C - * library functions (unlink() is an explicit exception, see - * http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html). - * That includes free(), so it doesn't try to free the 'files' data - * structure. */ +static void - cancel_files(void *aux UNUSED) ++cancel_files(void *aux OVS_UNUSED) +{ + shash_clear(&files); + added_hook = false; +} + static void do_unlink_files(void) { diff --cc lib/netdev-linux.c index 6d46b09e,95501b08..5341ed06 --- a/lib/netdev-linux.c +++ b/lib/netdev-linux.c @@@ -216,411 -192,50 +216,411 @@@ netdev_linux_wait(void static void netdev_linux_cache_cb(const struct rtnetlink_change *change, - void *aux UNUSED) + void *aux OVS_UNUSED) { - struct netdev_linux_cache *cache; + struct netdev_dev_linux *dev; if (change) { - cache = shash_find_data(&cache_map, change->ifname); - if (cache) { - cache->valid = 0; + struct netdev_dev *base_dev = netdev_dev_from_name(change->ifname); + if (base_dev) { + dev = netdev_dev_linux_cast(base_dev); + dev->cache_valid = 0; } } else { + struct shash device_shash; struct shash_node *node; - SHASH_FOR_EACH (node, &cache_map) { - cache = node->data; - cache->valid = 0; + + shash_init(&device_shash); + netdev_dev_get_devices(&netdev_linux_class, &device_shash); + SHASH_FOR_EACH (node, &device_shash) { + dev = node->data; + dev->cache_valid = 0; } + shash_destroy(&device_shash); } } -/* Creates the netdev object of 'type' with 'name'. */ +/* The arguments are marked as unused to prevent warnings on platforms where + * the Netlink interface isn't supported. */ static int - setup_gre_netlink(const char *name UNUSED, struct gre_config *config UNUSED, - bool create UNUSED) -netdev_linux_create(const char *name, const char *type, - const struct shash *args, bool created) ++setup_gre_netlink(const char *name OVS_UNUSED, ++ struct gre_config *config OVS_UNUSED, bool create OVS_UNUSED) { - struct netdev_obj_linux *netdev_obj; - static const char tap_dev[] = "/dev/net/tun"; +#ifdef GRE_IOCTL_ONLY + return EOPNOTSUPP; +#else + int error; + struct ofpbuf request, *reply; + unsigned int nl_flags; + struct ifinfomsg ifinfomsg; + struct nlattr *linkinfo_hdr; + struct nlattr *info_data_hdr; + uint16_t iflags = 0; + uint16_t oflags = 0; + uint8_t pmtudisc = 0; + + VLOG_DBG("%s: attempting to create gre device using netlink", name); + + if (!gre_descriptors.nl_sock) { + error = nl_sock_create(NETLINK_ROUTE, 0, 0, 0, + &gre_descriptors.nl_sock); + if (error) { + VLOG_WARN("couldn't create netlink socket: %s", strerror(error)); + goto error; + } + } + + ofpbuf_init(&request, 0); + + nl_flags = NLM_F_REQUEST; + if (create) { + nl_flags |= NLM_F_CREATE|NLM_F_EXCL; + } + + /* We over-reserve space, because we do some pointer arithmetic + * and don't want the buffer address shifting under us. */ + nl_msg_put_nlmsghdr(&request, gre_descriptors.nl_sock, 2048, RTM_NEWLINK, + nl_flags); + + memset(&ifinfomsg, 0, sizeof ifinfomsg); + ifinfomsg.ifi_family = AF_UNSPEC; + nl_msg_put(&request, &ifinfomsg, sizeof ifinfomsg); + + linkinfo_hdr = ofpbuf_tail(&request); + nl_msg_put_unspec(&request, IFLA_LINKINFO, NULL, 0); + + nl_msg_put_unspec(&request, IFLA_INFO_KIND, "gretap", 6); + + info_data_hdr = ofpbuf_tail(&request); + nl_msg_put_unspec(&request, IFLA_INFO_DATA, NULL, 0); + + /* Set flags */ + if (config->have_in_key) { + iflags |= GRE_KEY; + } + if (config->have_out_key) { + oflags |= GRE_KEY; + } + + if (config->in_csum) { + iflags |= GRE_CSUM; + } + if (config->out_csum) { + oflags |= GRE_CSUM; + } + + /* Add options */ + nl_msg_put_u32(&request, IFLA_GRE_IKEY, config->in_key); + nl_msg_put_u32(&request, IFLA_GRE_OKEY, config->out_key); + nl_msg_put_u16(&request, IFLA_GRE_IFLAGS, iflags); + nl_msg_put_u16(&request, IFLA_GRE_OFLAGS, oflags); + nl_msg_put_u32(&request, IFLA_GRE_LOCAL, config->local_ip); + nl_msg_put_u32(&request, IFLA_GRE_REMOTE, config->remote_ip); + nl_msg_put_u8(&request, IFLA_GRE_PMTUDISC, pmtudisc); + nl_msg_put_u8(&request, IFLA_GRE_TTL, 0); + nl_msg_put_u8(&request, IFLA_GRE_TOS, 0); + + info_data_hdr->nla_len = (char *)ofpbuf_tail(&request) + - (char *)info_data_hdr; + linkinfo_hdr->nla_len = (char *)ofpbuf_tail(&request) + - (char *)linkinfo_hdr; + + nl_msg_put_string(&request, IFLA_IFNAME, name); + + error = nl_sock_transact(gre_descriptors.nl_sock, &request, &reply); + ofpbuf_uninit(&request); + if (error) { + VLOG_WARN("couldn't transact netlink socket: %s", strerror(error)); + goto error; + } + ofpbuf_delete(reply); + +error: + return error; +#endif +} + +static int +setup_gre_ioctl(const char *name, struct gre_config *config, bool create) +{ + struct ip_tunnel_parm p; struct ifreq ifr; + + VLOG_DBG("%s: attempting to create gre device using ioctl", name); + + memset(&p, 0, sizeof p); + + strncpy(p.name, name, IFNAMSIZ); + + p.iph.version = 4; + p.iph.ihl = 5; + p.iph.protocol = IPPROTO_GRE; + p.iph.saddr = config->local_ip; + p.iph.daddr = config->remote_ip; + + if (config->have_in_key) { + p.i_flags |= GRE_KEY; + p.i_key = config->in_key; + } + if (config->have_out_key) { + p.o_flags |= GRE_KEY; + p.o_key = config->out_key; + } + + if (config->in_csum) { + p.i_flags |= GRE_CSUM; + } + if (config->out_csum) { + p.o_flags |= GRE_CSUM; + } + + strncpy(ifr.ifr_name, create ? GRE_IOCTL_DEVICE : name, IFNAMSIZ); + ifr.ifr_ifru.ifru_data = (void *)&p; + + if (!gre_descriptors.ioctl_fd) { + gre_descriptors.ioctl_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (gre_descriptors.ioctl_fd < 0) { + VLOG_WARN("couldn't create gre ioctl socket: %s", strerror(errno)); + gre_descriptors.ioctl_fd = 0; + return errno; + } + } + + if (ioctl(gre_descriptors.ioctl_fd, create ? SIOCADDGRETAP : SIOCCHGGRETAP, + &ifr) < 0) { + VLOG_WARN("couldn't do gre ioctl: %s", strerror(errno)); + return errno; + } + + return 0; +} + +/* The arguments are marked as unused to prevent warnings on platforms where + * the Netlink interface isn't supported. */ +static bool - check_gre_device_netlink(const char *name UNUSED) ++check_gre_device_netlink(const char *name OVS_UNUSED) +{ +#ifdef GRE_IOCTL_ONLY + return false; +#else + static const struct nl_policy getlink_policy[] = { + [IFLA_LINKINFO] = { .type = NL_A_NESTED, .optional = false }, + }; + + static const struct nl_policy linkinfo_policy[] = { + [IFLA_INFO_KIND] = { .type = NL_A_STRING, .optional = false }, + }; + + int error; + bool ret = false; + struct ofpbuf request, *reply; + struct ifinfomsg ifinfomsg; + struct nlattr *getlink_attrs[ARRAY_SIZE(getlink_policy)]; + struct nlattr *linkinfo_attrs[ARRAY_SIZE(linkinfo_policy)]; + struct ofpbuf linkinfo; + const char *device_kind; + + ofpbuf_init(&request, 0); + + nl_msg_put_nlmsghdr(&request, gre_descriptors.nl_sock, + NLMSG_LENGTH(sizeof ifinfomsg), RTM_GETLINK, + NLM_F_REQUEST); + + memset(&ifinfomsg, 0, sizeof ifinfomsg); + ifinfomsg.ifi_family = AF_UNSPEC; + ifinfomsg.ifi_index = do_get_ifindex(name); + nl_msg_put(&request, &ifinfomsg, sizeof ifinfomsg); + + error = nl_sock_transact(gre_descriptors.nl_sock, &request, &reply); + ofpbuf_uninit(&request); + if (error) { + VLOG_WARN("couldn't transact netlink socket: %s", strerror(error)); + return false; + } + + if (!nl_policy_parse(reply, NLMSG_HDRLEN + sizeof(struct ifinfomsg), + getlink_policy, getlink_attrs, + ARRAY_SIZE(getlink_policy))) { + VLOG_WARN("received bad rtnl message (getlink policy)"); + goto error; + } + + linkinfo.data = (void *)nl_attr_get(getlink_attrs[IFLA_LINKINFO]); + linkinfo.size = nl_attr_get_size(getlink_attrs[IFLA_LINKINFO]); + if (!nl_policy_parse(&linkinfo, 0, linkinfo_policy, + linkinfo_attrs, ARRAY_SIZE(linkinfo_policy))) { + VLOG_WARN("received bad rtnl message (linkinfo policy)"); + goto error; + } + + device_kind = nl_attr_get_string(linkinfo_attrs[IFLA_INFO_KIND]); + ret = !strcmp(device_kind, "gretap"); + +error: + ofpbuf_delete(reply); + return ret; +#endif +} + +static bool +check_gre_device_ioctl(const char *name) +{ + struct ethtool_drvinfo drvinfo; + int error; + + memset(&drvinfo, 0, sizeof drvinfo); + error = netdev_linux_do_ethtool(name, (struct ethtool_cmd *)&drvinfo, + ETHTOOL_GDRVINFO, "ETHTOOL_GDRVINFO"); + + return !error && !strcmp(drvinfo.driver, "ip_gre") + && !strcmp(drvinfo.bus_info, "gretap"); +} + +static int +setup_gre(const char *name, const struct shash *args, bool create) +{ + int error; + struct in_addr in_addr; + struct shash_node *node; + struct gre_config config; + + memset(&config, 0, sizeof config); + config.in_csum = true; + config.out_csum = true; + + SHASH_FOR_EACH (node, args) { + if (!strcmp(node->name, "remote_ip")) { + if (lookup_ip(node->data, &in_addr)) { + VLOG_WARN("bad 'remote_ip' for gre device %s ", name); + } else { + config.remote_ip = in_addr.s_addr; + } + } else if (!strcmp(node->name, "local_ip")) { + if (lookup_ip(node->data, &in_addr)) { + VLOG_WARN("bad 'local_ip' for gre device %s ", name); + } else { + config.local_ip = in_addr.s_addr; + } + } else if (!strcmp(node->name, "key")) { + config.have_in_key = true; + config.have_out_key = true; + config.in_key = htonl(atoi(node->data)); + config.out_key = htonl(atoi(node->data)); + } else if (!strcmp(node->name, "in_key")) { + config.have_in_key = true; + config.in_key = htonl(atoi(node->data)); + } else if (!strcmp(node->name, "out_key")) { + config.have_out_key = true; + config.out_key = htonl(atoi(node->data)); + } else if (!strcmp(node->name, "csum")) { + if (!strcmp(node->data, "false")) { + config.in_csum = false; + config.out_csum = false; + } + } else { + VLOG_WARN("unknown gre argument '%s'", node->name); + } + } + + if (!config.remote_ip) { + VLOG_WARN("gre type requires valid 'remote_ip' argument"); + error = EINVAL; + goto error; + } + + if (!gre_descriptors.use_ioctl) { + error = setup_gre_netlink(name, &config, create); + if (error == EOPNOTSUPP) { + gre_descriptors.use_ioctl = true; + } + } + if (gre_descriptors.use_ioctl) { + error = setup_gre_ioctl(name, &config, create); + } + + if (create && error == EEXIST) { + bool gre_device; + + if (gre_descriptors.use_ioctl) { + gre_device = check_gre_device_ioctl(name); + } else { + gre_device = check_gre_device_netlink(name); + } + + if (!gre_device) { + goto error; + } + + VLOG_WARN("replacing existing gre device %s", name); + error = destroy_gre(name); + if (error) { + goto error; + } + + if (gre_descriptors.use_ioctl) { + error = setup_gre_ioctl(name, &config, create); + } else { + error = setup_gre_netlink(name, &config, create); + } + } + +error: + return error; +} + +/* Creates the netdev device of 'type' with 'name'. */ +static int - netdev_linux_create_system(const char *name, const char *type UNUSED, ++netdev_linux_create_system(const char *name, const char *type OVS_UNUSED, + const struct shash *args, struct netdev_dev **netdev_devp) +{ + struct netdev_dev_linux *netdev_dev; int error; if (!shash_is_empty(args)) { - VLOG_WARN("arguments for %s devices should be empty", type); + VLOG_WARN("%s: arguments for system devices should be empty", name); } - /* Create the name binding in the netdev library for this object. */ - netdev_obj = xcalloc(1, sizeof *netdev_obj); - netdev_obj_init(&netdev_obj->netdev_obj, name, &netdev_linux_class, - created); - netdev_obj->tap_fd = -1; + if (!cache_notifier_refcount) { + error = rtnetlink_notifier_register(&netdev_linux_cache_notifier, + netdev_linux_cache_cb, NULL); + if (error) { + return error; + } + } + cache_notifier_refcount++; - if (strcmp(type, "tap")) { - return 0; + netdev_dev = xzalloc(sizeof *netdev_dev); + netdev_dev_init(&netdev_dev->netdev_dev, name, &netdev_linux_class); + + *netdev_devp = &netdev_dev->netdev_dev; + return 0; +} + +/* For most types of netdevs we open the device for each call of + * netdev_open(). However, this is not the case with tap devices, + * since it is only possible to open the device once. In this + * situation we share a single file descriptor, and consequently + * buffers, across all readers. Therefore once data is read it will + * be unavailable to other reads for tap devices. */ +static int - netdev_linux_create_tap(const char *name, const char *type UNUSED, ++netdev_linux_create_tap(const char *name, const char *type OVS_UNUSED, + const struct shash *args, struct netdev_dev **netdev_devp) +{ + struct netdev_dev_linux *netdev_dev; + struct tap_state *state; + static const char tap_dev[] = "/dev/net/tun"; + struct ifreq ifr; + int error; + + if (!shash_is_empty(args)) { + VLOG_WARN("%s: arguments for TAP devices should be empty", name); } + netdev_dev = xzalloc(sizeof *netdev_dev); + state = &netdev_dev->state.tap; + /* Open tap device. */ - netdev_obj->tap_fd = open(tap_dev, O_RDWR); - if (netdev_obj->tap_fd < 0) { + state->fd = open(tap_dev, O_RDWR); + if (state->fd < 0) { error = errno; VLOG_WARN("opening \"%s\" failed: %s", tap_dev, strerror(error)); goto error; @@@ -669,158 -279,64 +669,158 @@@ if_up(const char *name } static int - netdev_linux_create_gre(const char *name, const char *type UNUSED, -netdev_linux_open(const char *name, int ethertype, struct netdev **netdevp) ++netdev_linux_create_gre(const char *name, const char *type OVS_UNUSED, + const struct shash *args, struct netdev_dev **netdev_devp) { - struct netdev_linux *netdev; - enum netdev_flags flags; + struct netdev_dev_linux *netdev_dev; int error; - /* Allocate network device. */ - netdev = xcalloc(1, sizeof *netdev); - netdev_init(&netdev->netdev, name, &netdev_linux_class); - netdev->netdev_fd = -1; - netdev->tap_fd = -1; - netdev->cache = shash_find_data(&cache_map, name); - if (!netdev->cache) { - if (shash_is_empty(&cache_map)) { - int error = rtnetlink_notifier_register( - &netdev_linux_cache_notifier, netdev_linux_cache_cb, NULL); - if (error) { - netdev_close(&netdev->netdev); - return error; - } - } - netdev->cache = xmalloc(sizeof *netdev->cache); - netdev->cache->shash_node = shash_add(&cache_map, name, - netdev->cache); - netdev->cache->valid = 0; - netdev->cache->ref_cnt = 0; + netdev_dev = xzalloc(sizeof *netdev_dev); + + error = setup_gre(name, args, true); + if (error) { + goto error; } - netdev->cache->ref_cnt++; - if (!strcmp(netdev_get_type(&netdev->netdev), "tap")) { - static const char tap_dev[] = "/dev/net/tun"; - struct ifreq ifr; + error = if_up(name); + if (error) { + goto error; + } - /* Open tap device. */ - netdev->tap_fd = open(tap_dev, O_RDWR); - if (netdev->tap_fd < 0) { - error = errno; - VLOG_WARN("opening \"%s\" failed: %s", tap_dev, strerror(error)); - goto error; - } + netdev_dev_init(&netdev_dev->netdev_dev, name, &netdev_gre_class); + *netdev_devp = &netdev_dev->netdev_dev; + return 0; - /* Create tap device. */ - ifr.ifr_flags = IFF_TAP | IFF_NO_PI; - strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name); - if (ioctl(netdev->tap_fd, TUNSETIFF, &ifr) == -1) { - VLOG_WARN("%s: creating tap device failed: %s", name, - strerror(errno)); - error = errno; - goto error; - } +error: + free(netdev_dev); + return error; +} - /* Make non-blocking. */ - error = set_nonblocking(netdev->tap_fd); - if (error) { - goto error; +static int +netdev_linux_reconfigure_gre(struct netdev_dev *netdev_dev_, + const struct shash *args) +{ + const char *name = netdev_dev_get_name(netdev_dev_); + + return setup_gre(name, args, false); +} + +/* The arguments are marked as unused to prevent warnings on platforms where + * the Netlink interface isn't supported. */ +static int - destroy_gre_netlink(const char *name UNUSED) ++destroy_gre_netlink(const char *name OVS_UNUSED) +{ +#ifdef GRE_IOCTL_ONLY + return EOPNOTSUPP; +#else + int error; + struct ofpbuf request, *reply; + struct ifinfomsg ifinfomsg; + int ifindex; + + ofpbuf_init(&request, 0); + + nl_msg_put_nlmsghdr(&request, gre_descriptors.nl_sock, 0, RTM_DELLINK, + NLM_F_REQUEST); + + memset(&ifinfomsg, 0, sizeof ifinfomsg); + ifinfomsg.ifi_family = AF_UNSPEC; + nl_msg_put(&request, &ifinfomsg, sizeof ifinfomsg); + + ifindex = do_get_ifindex(name); + nl_msg_put_u32(&request, IFLA_LINK, ifindex); + + nl_msg_put_string(&request, IFLA_IFNAME, name); + + error = nl_sock_transact(gre_descriptors.nl_sock, &request, &reply); + ofpbuf_uninit(&request); + if (error) { + VLOG_WARN("couldn't transact netlink socket: %s", strerror(error)); + goto error; + } + ofpbuf_delete(reply); + +error: + return 0; +#endif +} + +static int +destroy_gre_ioctl(const char *name) +{ + struct ip_tunnel_parm p; + struct ifreq ifr; + + memset(&p, 0, sizeof p); + strncpy(p.name, name, IFNAMSIZ); + + strncpy(ifr.ifr_name, name, IFNAMSIZ); + ifr.ifr_ifru.ifru_data = (void *)&p; + + if (ioctl(gre_descriptors.ioctl_fd, SIOCDELGRETAP, &ifr) < 0) { + VLOG_WARN("couldn't do gre ioctl: %s\n", strerror(errno)); + return errno; + } + + return 0; +} + +static void +destroy_tap(struct netdev_dev_linux *netdev_dev) +{ + struct tap_state *state = &netdev_dev->state.tap; + + if (state->fd >= 0) { + close(state->fd); + } +} + +static int +destroy_gre(const char *name) +{ + if (gre_descriptors.use_ioctl) { + return destroy_gre_ioctl(name); + } else { + return destroy_gre_netlink(name); + } +} + +/* Destroys the netdev device 'netdev_dev_'. */ +static void +netdev_linux_destroy(struct netdev_dev *netdev_dev_) +{ + struct netdev_dev_linux *netdev_dev = netdev_dev_linux_cast(netdev_dev_); + const char *type = netdev_dev_get_type(netdev_dev_); + + if (!strcmp(type, "system")) { + cache_notifier_refcount--; + + if (!cache_notifier_refcount) { + rtnetlink_notifier_unregister(&netdev_linux_cache_notifier); } + } else if (!strcmp(type, "tap")) { + destroy_tap(netdev_dev); + } else if (!strcmp(type, "gre")) { + destroy_gre(netdev_dev_get_name(&netdev_dev->netdev_dev)); } + free(netdev_dev_); +} + +static int +netdev_linux_open(struct netdev_dev *netdev_dev_, int ethertype, + struct netdev **netdevp) +{ + struct netdev_dev_linux *netdev_dev = netdev_dev_linux_cast(netdev_dev_); + struct netdev_linux *netdev; + enum netdev_flags flags; + int error; + + /* Allocate network device. */ + netdev = xzalloc(sizeof *netdev); + netdev->fd = -1; + netdev_init(&netdev->netdev, netdev_dev_); + error = netdev_get_flags(&netdev->netdev, &flags); if (error == ENODEV) { goto error; diff --cc lib/netdev.c index 78d6a924,82a49041..2c7b260e --- a/lib/netdev.c +++ b/lib/netdev.c @@@ -59,11 -56,16 +59,11 @@@ static struct list netdev_list = LIST_I * additional log messages. */ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20); - static void close_all_netdevs(void *aux UNUSED); -static void restore_all_flags(void *aux); ++static void close_all_netdevs(void *aux OVS_UNUSED); static int restore_flags(struct netdev *netdev); +void update_device_args(struct netdev_dev *, const struct shash *args); -/* Attempts to initialize the netdev module. Returns 0 if successful, - * otherwise a positive errno value. - * - * Calling this function is optional. If not called explicitly, it will - * automatically be called upon the first attempt to open or create a - * network device. */ -int +static void netdev_initialize(void) { static int status = -1; @@@ -1262,13 -1051,13 +1262,13 @@@ restore_flags(struct netdev *netdev return 0; } -/* Retores all the flags on all network devices that we modified. Called from - * a signal handler, so it does not attempt to report error conditions. */ +/* Close all netdevs on shutdown so they can do any needed cleanup such as + * destroying devices, restoring flags, etc. */ static void - close_all_netdevs(void *aux UNUSED) -restore_all_flags(void *aux OVS_UNUSED) ++close_all_netdevs(void *aux OVS_UNUSED) { - struct netdev *netdev; - LIST_FOR_EACH (netdev, struct netdev, node, &netdev_list) { - restore_flags(netdev); + struct netdev *netdev, *next; + LIST_FOR_EACH_SAFE(netdev, next, struct netdev, node, &netdev_list) { + netdev_close(netdev); } } diff --cc lib/packets.h index 6513f64b,2c33078f..eaffca93 --- a/lib/packets.h +++ b/lib/packets.h @@@ -26,11 -26,9 +26,11 @@@ struct ofpbuf; +bool dpid_from_string(const char *s, uint64_t *dpidp); + #define ETH_ADDR_LEN 6 - static const uint8_t eth_addr_broadcast[ETH_ADDR_LEN] UNUSED + static const uint8_t eth_addr_broadcast[ETH_ADDR_LEN] OVS_UNUSED = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; static inline bool eth_addr_is_broadcast(const uint8_t ea[6]) diff --cc lib/shash.h index 52cd4dca,471918b2..3a6e2778 --- a/lib/shash.h +++ b/lib/shash.h @@@ -50,9 -50,10 +54,13 @@@ bool shash_add_once(struct shash *, con void shash_delete(struct shash *, struct shash_node *); struct shash_node *shash_find(const struct shash *, const char *); void *shash_find_data(const struct shash *, const char *); +void *shash_find_and_delete(struct shash *, const char *); struct shash_node *shash_first(const struct shash *); +const struct shash_node **shash_sort(const struct shash *); +bool shash_equal_keys(const struct shash *, const struct shash *); + #ifdef __cplusplus + } + #endif + #endif /* shash.h */ diff --cc lib/stream-ssl.c index 941f7791,00000000..51ce3063 mode 100644,000000..100644 --- a/lib/stream-ssl.c +++ b/lib/stream-ssl.c @@@ -1,1089 -1,0 +1,1089 @@@ +/* + * Copyright (c) 2008, 2009, 2010 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "stream-ssl.h" +#include "dhparams.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dynamic-string.h" +#include "leak-checker.h" +#include "ofpbuf.h" +#include "openflow/openflow.h" +#include "packets.h" +#include "poll-loop.h" +#include "socket-util.h" +#include "socket-util.h" +#include "util.h" +#include "stream-provider.h" +#include "stream.h" + +#include "vlog.h" +#define THIS_MODULE VLM_stream_ssl + +/* Active SSL. */ + +enum ssl_state { + STATE_TCP_CONNECTING, + STATE_SSL_CONNECTING +}; + +enum session_type { + CLIENT, + SERVER +}; + +struct ssl_stream +{ + struct stream stream; + enum ssl_state state; + int connect_error; + enum session_type type; + int fd; + SSL *ssl; + struct ofpbuf *txbuf; + + /* rx_want and tx_want record the result of the last call to SSL_read() + * and SSL_write(), respectively: + * + * - If the call reported that data needed to be read from the file + * descriptor, the corresponding member is set to SSL_READING. + * + * - If the call reported that data needed to be written to the file + * descriptor, the corresponding member is set to SSL_WRITING. + * + * - Otherwise, the member is set to SSL_NOTHING, indicating that the + * call completed successfully (or with an error) and that there is no + * need to block. + * + * These are needed because there is no way to ask OpenSSL what a data read + * or write would require without giving it a buffer to receive into or + * data to send, respectively. (Note that the SSL_want() status is + * overwritten by each SSL_read() or SSL_write() call, so we can't rely on + * its value.) + * + * A single call to SSL_read() or SSL_write() can perform both reading + * and writing and thus invalidate not one of these values but actually + * both. Consider this situation, for example: + * + * - SSL_write() blocks on a read, so tx_want gets SSL_READING. + * + * - SSL_read() laters succeeds reading from 'fd' and clears out the + * whole receive buffer, so rx_want gets SSL_READING. + * + * - Client calls stream_wait(STREAM_RECV) and stream_wait(STREAM_SEND) + * and blocks. + * + * - Now we're stuck blocking until the peer sends us data, even though + * SSL_write() could now succeed, which could easily be a deadlock + * condition. + * + * On the other hand, we can't reset both tx_want and rx_want on every call + * to SSL_read() or SSL_write(), because that would produce livelock, + * e.g. in this situation: + * + * - SSL_write() blocks, so tx_want gets SSL_READING or SSL_WRITING. + * + * - SSL_read() blocks, so rx_want gets SSL_READING or SSL_WRITING, + * but tx_want gets reset to SSL_NOTHING. + * + * - Client calls stream_wait(STREAM_RECV) and stream_wait(STREAM_SEND) + * and blocks. + * + * - Client wakes up immediately since SSL_NOTHING in tx_want indicates + * that no blocking is necessary. + * + * The solution we adopt here is to set tx_want to SSL_NOTHING after + * calling SSL_read() only if the SSL state of the connection changed, + * which indicates that an SSL-level renegotiation made some progress, and + * similarly for rx_want and SSL_write(). This prevents both the + * deadlock and livelock situations above. + */ + int rx_want, tx_want; +}; + +/* SSL context created by ssl_init(). */ +static SSL_CTX *ctx; + +/* Required configuration. */ +static bool has_private_key, has_certificate, has_ca_cert; + +/* Ordinarily, we require a CA certificate for the peer to be locally + * available. 'has_ca_cert' is true when this is the case, and neither of the + * following variables matter. + * + * We can, however, bootstrap the CA certificate from the peer at the beginning + * of our first connection then use that certificate on all subsequent + * connections, saving it to a file for use in future runs also. In this case, + * 'has_ca_cert' is false, 'bootstrap_ca_cert' is true, and 'ca_cert_file' + * names the file to be saved. */ +static bool bootstrap_ca_cert; +static char *ca_cert_file; + +/* Who knows what can trigger various SSL errors, so let's throttle them down + * quite a bit. */ +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 25); + +static int ssl_init(void); +static int do_ssl_init(void); +static bool ssl_wants_io(int ssl_error); +static void ssl_close(struct stream *); +static void ssl_clear_txbuf(struct ssl_stream *); +static int interpret_ssl_error(const char *function, int ret, int error, + int *want); - static DH *tmp_dh_callback(SSL *ssl, int is_export UNUSED, int keylength); ++static DH *tmp_dh_callback(SSL *ssl, int is_export OVS_UNUSED, int keylength); +static void log_ca_cert(const char *file_name, X509 *cert); + +static short int +want_to_poll_events(int want) +{ + switch (want) { + case SSL_NOTHING: + NOT_REACHED(); + + case SSL_READING: + return POLLIN; + + case SSL_WRITING: + return POLLOUT; + + default: + NOT_REACHED(); + } +} + +static int +new_ssl_stream(const char *name, int fd, enum session_type type, + enum ssl_state state, const struct sockaddr_in *remote, + struct stream **streamp) +{ + struct sockaddr_in local; + socklen_t local_len = sizeof local; + struct ssl_stream *sslv; + SSL *ssl = NULL; + int on = 1; + int retval; + + /* Check for all the needful configuration. */ + retval = 0; + if (!has_private_key) { + VLOG_ERR("Private key must be configured to use SSL"); + retval = ENOPROTOOPT; + } + if (!has_certificate) { + VLOG_ERR("Certificate must be configured to use SSL"); + retval = ENOPROTOOPT; + } + if (!has_ca_cert && !bootstrap_ca_cert) { + VLOG_ERR("CA certificate must be configured to use SSL"); + retval = ENOPROTOOPT; + } + if (!SSL_CTX_check_private_key(ctx)) { + VLOG_ERR("Private key does not match certificate public key: %s", + ERR_error_string(ERR_get_error(), NULL)); + retval = ENOPROTOOPT; + } + if (retval) { + goto error; + } + + /* Get the local IP and port information */ + retval = getsockname(fd, (struct sockaddr *) &local, &local_len); + if (retval) { + memset(&local, 0, sizeof local); + } + + /* Disable Nagle. */ + retval = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof on); + if (retval) { + VLOG_ERR("%s: setsockopt(TCP_NODELAY): %s", name, strerror(errno)); + retval = errno; + goto error; + } + + /* Create and configure OpenSSL stream. */ + ssl = SSL_new(ctx); + if (ssl == NULL) { + VLOG_ERR("SSL_new: %s", ERR_error_string(ERR_get_error(), NULL)); + retval = ENOPROTOOPT; + goto error; + } + if (SSL_set_fd(ssl, fd) == 0) { + VLOG_ERR("SSL_set_fd: %s", ERR_error_string(ERR_get_error(), NULL)); + retval = ENOPROTOOPT; + goto error; + } + if (bootstrap_ca_cert && type == CLIENT) { + SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL); + } + + /* Create and return the ssl_stream. */ + sslv = xmalloc(sizeof *sslv); + stream_init(&sslv->stream, &ssl_stream_class, EAGAIN, name); + stream_set_remote_ip(&sslv->stream, remote->sin_addr.s_addr); + stream_set_remote_port(&sslv->stream, remote->sin_port); + stream_set_local_ip(&sslv->stream, local.sin_addr.s_addr); + stream_set_local_port(&sslv->stream, local.sin_port); + sslv->state = state; + sslv->type = type; + sslv->fd = fd; + sslv->ssl = ssl; + sslv->txbuf = NULL; + sslv->rx_want = sslv->tx_want = SSL_NOTHING; + *streamp = &sslv->stream; + return 0; + +error: + if (ssl) { + SSL_free(ssl); + } + close(fd); + return retval; +} + +static struct ssl_stream * +ssl_stream_cast(struct stream *stream) +{ + stream_assert_class(stream, &ssl_stream_class); + return CONTAINER_OF(stream, struct ssl_stream, stream); +} + +static int +ssl_open(const char *name, char *suffix, struct stream **streamp) +{ + struct sockaddr_in sin; + int error, fd; + + error = ssl_init(); + if (error) { + return error; + } + + error = inet_open_active(SOCK_STREAM, suffix, OFP_SSL_PORT, &sin, &fd); + if (fd >= 0) { + int state = error ? STATE_TCP_CONNECTING : STATE_SSL_CONNECTING; + return new_ssl_stream(name, fd, CLIENT, state, &sin, streamp); + } else { + VLOG_ERR("%s: connect: %s", name, strerror(error)); + return error; + } +} + +static int +do_ca_cert_bootstrap(struct stream *stream) +{ + struct ssl_stream *sslv = ssl_stream_cast(stream); + STACK_OF(X509) *chain; + X509 *ca_cert; + FILE *file; + int error; + int fd; + + chain = SSL_get_peer_cert_chain(sslv->ssl); + if (!chain || !sk_X509_num(chain)) { + VLOG_ERR("could not bootstrap CA cert: no certificate presented by " + "peer"); + return EPROTO; + } + ca_cert = sk_X509_value(chain, sk_X509_num(chain) - 1); + + /* Check that 'ca_cert' is self-signed. Otherwise it is not a CA + * certificate and we should not attempt to use it as one. */ + error = X509_check_issued(ca_cert, ca_cert); + if (error) { + VLOG_ERR("could not bootstrap CA cert: obtained certificate is " + "not self-signed (%s)", + X509_verify_cert_error_string(error)); + if (sk_X509_num(chain) < 2) { + VLOG_ERR("only one certificate was received, so probably the peer " + "is not configured to send its CA certificate"); + } + return EPROTO; + } + + fd = open(ca_cert_file, O_CREAT | O_EXCL | O_WRONLY, 0444); + if (fd < 0) { + VLOG_ERR("could not bootstrap CA cert: creating %s failed: %s", + ca_cert_file, strerror(errno)); + return errno; + } + + file = fdopen(fd, "w"); + if (!file) { + int error = errno; + VLOG_ERR("could not bootstrap CA cert: fdopen failed: %s", + strerror(error)); + unlink(ca_cert_file); + return error; + } + + if (!PEM_write_X509(file, ca_cert)) { + VLOG_ERR("could not bootstrap CA cert: PEM_write_X509 to %s failed: " + "%s", ca_cert_file, ERR_error_string(ERR_get_error(), NULL)); + fclose(file); + unlink(ca_cert_file); + return EIO; + } + + if (fclose(file)) { + int error = errno; + VLOG_ERR("could not bootstrap CA cert: writing %s failed: %s", + ca_cert_file, strerror(error)); + unlink(ca_cert_file); + return error; + } + + VLOG_INFO("successfully bootstrapped CA cert to %s", ca_cert_file); + log_ca_cert(ca_cert_file, ca_cert); + bootstrap_ca_cert = false; + has_ca_cert = true; + + /* SSL_CTX_add_client_CA makes a copy of ca_cert's relevant data. */ + SSL_CTX_add_client_CA(ctx, ca_cert); + + /* SSL_CTX_use_certificate() takes ownership of the certificate passed in. + * 'ca_cert' is owned by sslv->ssl, so we need to duplicate it. */ + ca_cert = X509_dup(ca_cert); + if (!ca_cert) { + out_of_memory(); + } + if (SSL_CTX_load_verify_locations(ctx, ca_cert_file, NULL) != 1) { + VLOG_ERR("SSL_CTX_load_verify_locations: %s", + ERR_error_string(ERR_get_error(), NULL)); + return EPROTO; + } + VLOG_INFO("killing successful connection to retry using CA cert"); + return EPROTO; +} + +static int +ssl_connect(struct stream *stream) +{ + struct ssl_stream *sslv = ssl_stream_cast(stream); + int retval; + + switch (sslv->state) { + case STATE_TCP_CONNECTING: + retval = check_connection_completion(sslv->fd); + if (retval) { + return retval; + } + sslv->state = STATE_SSL_CONNECTING; + /* Fall through. */ + + case STATE_SSL_CONNECTING: + retval = (sslv->type == CLIENT + ? SSL_connect(sslv->ssl) : SSL_accept(sslv->ssl)); + if (retval != 1) { + int error = SSL_get_error(sslv->ssl, retval); + if (retval < 0 && ssl_wants_io(error)) { + return EAGAIN; + } else { + int unused; + interpret_ssl_error((sslv->type == CLIENT ? "SSL_connect" + : "SSL_accept"), retval, error, &unused); + shutdown(sslv->fd, SHUT_RDWR); + return EPROTO; + } + } else if (bootstrap_ca_cert) { + return do_ca_cert_bootstrap(stream); + } else if ((SSL_get_verify_mode(sslv->ssl) + & (SSL_VERIFY_NONE | SSL_VERIFY_PEER)) + != SSL_VERIFY_PEER) { + /* Two or more SSL connections completed at the same time while we + * were in bootstrap mode. Only one of these can finish the + * bootstrap successfully. The other one(s) must be rejected + * because they were not verified against the bootstrapped CA + * certificate. (Alternatively we could verify them against the CA + * certificate, but that's more trouble than it's worth. These + * connections will succeed the next time they retry, assuming that + * they have a certificate against the correct CA.) */ + VLOG_ERR("rejecting SSL connection during bootstrap race window"); + return EPROTO; + } else { + return 0; + } + } + + NOT_REACHED(); +} + +static void +ssl_close(struct stream *stream) +{ + struct ssl_stream *sslv = ssl_stream_cast(stream); + ssl_clear_txbuf(sslv); + + /* Attempt clean shutdown of the SSL connection. This will work most of + * the time, as long as the kernel send buffer has some free space and the + * SSL connection isn't renegotiating, etc. That has to be good enough, + * since we don't have any way to continue the close operation in the + * background. */ + SSL_shutdown(sslv->ssl); + + SSL_free(sslv->ssl); + close(sslv->fd); + free(sslv); +} + +static int +interpret_ssl_error(const char *function, int ret, int error, + int *want) +{ + *want = SSL_NOTHING; + + switch (error) { + case SSL_ERROR_NONE: + VLOG_ERR_RL(&rl, "%s: unexpected SSL_ERROR_NONE", function); + break; + + case SSL_ERROR_ZERO_RETURN: + VLOG_ERR_RL(&rl, "%s: unexpected SSL_ERROR_ZERO_RETURN", function); + break; + + case SSL_ERROR_WANT_READ: + *want = SSL_READING; + return EAGAIN; + + case SSL_ERROR_WANT_WRITE: + *want = SSL_WRITING; + return EAGAIN; + + case SSL_ERROR_WANT_CONNECT: + VLOG_ERR_RL(&rl, "%s: unexpected SSL_ERROR_WANT_CONNECT", function); + break; + + case SSL_ERROR_WANT_ACCEPT: + VLOG_ERR_RL(&rl, "%s: unexpected SSL_ERROR_WANT_ACCEPT", function); + break; + + case SSL_ERROR_WANT_X509_LOOKUP: + VLOG_ERR_RL(&rl, "%s: unexpected SSL_ERROR_WANT_X509_LOOKUP", + function); + break; + + case SSL_ERROR_SYSCALL: { + int queued_error = ERR_get_error(); + if (queued_error == 0) { + if (ret < 0) { + int status = errno; + VLOG_WARN_RL(&rl, "%s: system error (%s)", + function, strerror(status)); + return status; + } else { + VLOG_WARN_RL(&rl, "%s: unexpected SSL connection close", + function); + return EPROTO; + } + } else { + VLOG_WARN_RL(&rl, "%s: %s", + function, ERR_error_string(queued_error, NULL)); + break; + } + } + + case SSL_ERROR_SSL: { + int queued_error = ERR_get_error(); + if (queued_error != 0) { + VLOG_WARN_RL(&rl, "%s: %s", + function, ERR_error_string(queued_error, NULL)); + } else { + VLOG_ERR_RL(&rl, "%s: SSL_ERROR_SSL without queued error", + function); + } + break; + } + + default: + VLOG_ERR_RL(&rl, "%s: bad SSL error code %d", function, error); + break; + } + return EIO; +} + +static ssize_t +ssl_recv(struct stream *stream, void *buffer, size_t n) +{ + struct ssl_stream *sslv = ssl_stream_cast(stream); + int old_state; + ssize_t ret; + + /* Behavior of zero-byte SSL_read is poorly defined. */ + assert(n > 0); + + old_state = SSL_get_state(sslv->ssl); + ret = SSL_read(sslv->ssl, buffer, n); + if (old_state != SSL_get_state(sslv->ssl)) { + sslv->tx_want = SSL_NOTHING; + } + sslv->rx_want = SSL_NOTHING; + + if (ret > 0) { + return ret; + } else { + int error = SSL_get_error(sslv->ssl, ret); + if (error == SSL_ERROR_ZERO_RETURN) { + return 0; + } else { + return -interpret_ssl_error("SSL_read", ret, error, + &sslv->rx_want); + } + } +} + +static void +ssl_clear_txbuf(struct ssl_stream *sslv) +{ + ofpbuf_delete(sslv->txbuf); + sslv->txbuf = NULL; +} + +static int +ssl_do_tx(struct stream *stream) +{ + struct ssl_stream *sslv = ssl_stream_cast(stream); + + for (;;) { + int old_state = SSL_get_state(sslv->ssl); + int ret = SSL_write(sslv->ssl, sslv->txbuf->data, sslv->txbuf->size); + if (old_state != SSL_get_state(sslv->ssl)) { + sslv->rx_want = SSL_NOTHING; + } + sslv->tx_want = SSL_NOTHING; + if (ret > 0) { + ofpbuf_pull(sslv->txbuf, ret); + if (sslv->txbuf->size == 0) { + return 0; + } + } else { + int ssl_error = SSL_get_error(sslv->ssl, ret); + if (ssl_error == SSL_ERROR_ZERO_RETURN) { + VLOG_WARN_RL(&rl, "SSL_write: connection closed"); + return EPIPE; + } else { + return interpret_ssl_error("SSL_write", ret, ssl_error, + &sslv->tx_want); + } + } + } +} + +static ssize_t +ssl_send(struct stream *stream, const void *buffer, size_t n) +{ + struct ssl_stream *sslv = ssl_stream_cast(stream); + + if (sslv->txbuf) { + return -EAGAIN; + } else { + int error; + + sslv->txbuf = ofpbuf_clone_data(buffer, n); + error = ssl_do_tx(stream); + switch (error) { + case 0: + ssl_clear_txbuf(sslv); + return n; + case EAGAIN: + leak_checker_claim(buffer); + return n; + default: + sslv->txbuf = NULL; + return -error; + } + } +} + +static void +ssl_run(struct stream *stream) +{ + struct ssl_stream *sslv = ssl_stream_cast(stream); + + if (sslv->txbuf && ssl_do_tx(stream) != EAGAIN) { + ssl_clear_txbuf(sslv); + } +} + +static void +ssl_run_wait(struct stream *stream) +{ + struct ssl_stream *sslv = ssl_stream_cast(stream); + + if (sslv->tx_want != SSL_NOTHING) { + poll_fd_wait(sslv->fd, want_to_poll_events(sslv->tx_want)); + } +} + +static void +ssl_wait(struct stream *stream, enum stream_wait_type wait) +{ + struct ssl_stream *sslv = ssl_stream_cast(stream); + + switch (wait) { + case STREAM_CONNECT: + if (stream_connect(stream) != EAGAIN) { + poll_immediate_wake(); + } else { + switch (sslv->state) { + case STATE_TCP_CONNECTING: + poll_fd_wait(sslv->fd, POLLOUT); + break; + + case STATE_SSL_CONNECTING: + /* ssl_connect() called SSL_accept() or SSL_connect(), which + * set up the status that we test here. */ + poll_fd_wait(sslv->fd, + want_to_poll_events(SSL_want(sslv->ssl))); + break; + + default: + NOT_REACHED(); + } + } + break; + + case STREAM_RECV: + if (sslv->rx_want != SSL_NOTHING) { + poll_fd_wait(sslv->fd, want_to_poll_events(sslv->rx_want)); + } else { + poll_immediate_wake(); + } + break; + + case STREAM_SEND: + if (!sslv->txbuf) { + /* We have room in our tx queue. */ + poll_immediate_wake(); + } else { + /* stream_run_wait() will do the right thing; don't bother with + * redundancy. */ + } + break; + + default: + NOT_REACHED(); + } +} + +struct stream_class ssl_stream_class = { + "ssl", /* name */ + ssl_open, /* open */ + ssl_close, /* close */ + ssl_connect, /* connect */ + ssl_recv, /* recv */ + ssl_send, /* send */ + ssl_run, /* run */ + ssl_run_wait, /* run_wait */ + ssl_wait, /* wait */ +}; + +/* Passive SSL. */ + +struct pssl_pstream +{ + struct pstream pstream; + int fd; +}; + +struct pstream_class pssl_pstream_class; + +static struct pssl_pstream * +pssl_pstream_cast(struct pstream *pstream) +{ + pstream_assert_class(pstream, &pssl_pstream_class); + return CONTAINER_OF(pstream, struct pssl_pstream, pstream); +} + +static int - pssl_open(const char *name UNUSED, char *suffix, struct pstream **pstreamp) ++pssl_open(const char *name OVS_UNUSED, char *suffix, struct pstream **pstreamp) +{ + struct pssl_pstream *pssl; + struct sockaddr_in sin; + char bound_name[128]; + int retval; + int fd; + + retval = ssl_init(); + if (retval) { + return retval; + } + + fd = inet_open_passive(SOCK_STREAM, suffix, OFP_SSL_PORT, &sin); + if (fd < 0) { + return -fd; + } + sprintf(bound_name, "pssl:%"PRIu16":"IP_FMT, + ntohs(sin.sin_port), IP_ARGS(&sin.sin_addr.s_addr)); + + pssl = xmalloc(sizeof *pssl); + pstream_init(&pssl->pstream, &pssl_pstream_class, bound_name); + pssl->fd = fd; + *pstreamp = &pssl->pstream; + return 0; +} + +static void +pssl_close(struct pstream *pstream) +{ + struct pssl_pstream *pssl = pssl_pstream_cast(pstream); + close(pssl->fd); + free(pssl); +} + +static int +pssl_accept(struct pstream *pstream, struct stream **new_streamp) +{ + struct pssl_pstream *pssl = pssl_pstream_cast(pstream); + struct sockaddr_in sin; + socklen_t sin_len = sizeof sin; + char name[128]; + int new_fd; + int error; + + new_fd = accept(pssl->fd, &sin, &sin_len); + if (new_fd < 0) { + int error = errno; + if (error != EAGAIN) { + VLOG_DBG_RL(&rl, "accept: %s", strerror(error)); + } + return error; + } + + error = set_nonblocking(new_fd); + if (error) { + close(new_fd); + return error; + } + + sprintf(name, "ssl:"IP_FMT, IP_ARGS(&sin.sin_addr)); + if (sin.sin_port != htons(OFP_SSL_PORT)) { + sprintf(strchr(name, '\0'), ":%"PRIu16, ntohs(sin.sin_port)); + } + return new_ssl_stream(name, new_fd, SERVER, STATE_SSL_CONNECTING, &sin, + new_streamp); +} + +static void +pssl_wait(struct pstream *pstream) +{ + struct pssl_pstream *pssl = pssl_pstream_cast(pstream); + poll_fd_wait(pssl->fd, POLLIN); +} + +struct pstream_class pssl_pstream_class = { + "pssl", + pssl_open, + pssl_close, + pssl_accept, + pssl_wait, +}; + +/* + * Returns true if OpenSSL error is WANT_READ or WANT_WRITE, indicating that + * OpenSSL is requesting that we call it back when the socket is ready for read + * or writing, respectively. + */ +static bool +ssl_wants_io(int ssl_error) +{ + return (ssl_error == SSL_ERROR_WANT_WRITE + || ssl_error == SSL_ERROR_WANT_READ); +} + +static int +ssl_init(void) +{ + static int init_status = -1; + if (init_status < 0) { + init_status = do_ssl_init(); + assert(init_status >= 0); + } + return init_status; +} + +static int +do_ssl_init(void) +{ + SSL_METHOD *method; + + SSL_library_init(); + SSL_load_error_strings(); + + method = TLSv1_method(); + if (method == NULL) { + VLOG_ERR("TLSv1_method: %s", ERR_error_string(ERR_get_error(), NULL)); + return ENOPROTOOPT; + } + + ctx = SSL_CTX_new(method); + if (ctx == NULL) { + VLOG_ERR("SSL_CTX_new: %s", ERR_error_string(ERR_get_error(), NULL)); + return ENOPROTOOPT; + } + SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); + SSL_CTX_set_tmp_dh_callback(ctx, tmp_dh_callback); + SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); + SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + NULL); + + return 0; +} + +static DH * - tmp_dh_callback(SSL *ssl UNUSED, int is_export UNUSED, int keylength) ++tmp_dh_callback(SSL *ssl OVS_UNUSED, int is_export OVS_UNUSED, int keylength) +{ + struct dh { + int keylength; + DH *dh; + DH *(*constructor)(void); + }; + + static struct dh dh_table[] = { + {1024, NULL, get_dh1024}, + {2048, NULL, get_dh2048}, + {4096, NULL, get_dh4096}, + }; + + struct dh *dh; + + for (dh = dh_table; dh < &dh_table[ARRAY_SIZE(dh_table)]; dh++) { + if (dh->keylength == keylength) { + if (!dh->dh) { + dh->dh = dh->constructor(); + if (!dh->dh) { + ovs_fatal(ENOMEM, "out of memory constructing " + "Diffie-Hellman parameters"); + } + } + return dh->dh; + } + } + VLOG_ERR_RL(&rl, "no Diffie-Hellman parameters for key length %d", + keylength); + return NULL; +} + +/* Returns true if SSL is at least partially configured. */ +bool +stream_ssl_is_configured(void) +{ + return has_private_key || has_certificate || has_ca_cert; +} + +void +stream_ssl_set_private_key_file(const char *file_name) +{ + if (ssl_init()) { + return; + } + if (SSL_CTX_use_PrivateKey_file(ctx, file_name, SSL_FILETYPE_PEM) != 1) { + VLOG_ERR("SSL_use_PrivateKey_file: %s", + ERR_error_string(ERR_get_error(), NULL)); + return; + } + has_private_key = true; +} + +void +stream_ssl_set_certificate_file(const char *file_name) +{ + if (ssl_init()) { + return; + } + if (SSL_CTX_use_certificate_chain_file(ctx, file_name) != 1) { + VLOG_ERR("SSL_use_certificate_file: %s", + ERR_error_string(ERR_get_error(), NULL)); + return; + } + has_certificate = true; +} + +/* Reads the X509 certificate or certificates in file 'file_name'. On success, + * stores the address of the first element in an array of pointers to + * certificates in '*certs' and the number of certificates in the array in + * '*n_certs', and returns 0. On failure, stores a null pointer in '*certs', 0 + * in '*n_certs', and returns a positive errno value. + * + * The caller is responsible for freeing '*certs'. */ +static int +read_cert_file(const char *file_name, X509 ***certs, size_t *n_certs) +{ + FILE *file; + size_t allocated_certs = 0; + + *certs = NULL; + *n_certs = 0; + + file = fopen(file_name, "r"); + if (!file) { + VLOG_ERR("failed to open %s for reading: %s", + file_name, strerror(errno)); + return errno; + } + + for (;;) { + X509 *certificate; + int c; + + /* Read certificate from file. */ + certificate = PEM_read_X509(file, NULL, NULL, NULL); + if (!certificate) { + size_t i; + + VLOG_ERR("PEM_read_X509 failed reading %s: %s", + file_name, ERR_error_string(ERR_get_error(), NULL)); + for (i = 0; i < *n_certs; i++) { + X509_free((*certs)[i]); + } + free(*certs); + *certs = NULL; + *n_certs = 0; + return EIO; + } + + /* Add certificate to array. */ + if (*n_certs >= allocated_certs) { + *certs = x2nrealloc(*certs, &allocated_certs, sizeof **certs); + } + (*certs)[(*n_certs)++] = certificate; + + /* Are there additional certificates in the file? */ + do { + c = getc(file); + } while (isspace(c)); + if (c == EOF) { + break; + } + ungetc(c, file); + } + fclose(file); + return 0; +} + + +/* Sets 'file_name' as the name of a file containing one or more X509 + * certificates to send to the peer. Typical use in OpenFlow is to send the CA + * certificate to the peer, which enables a switch to pick up the controller's + * CA certificate on its first connection. */ +void +stream_ssl_set_peer_ca_cert_file(const char *file_name) +{ + X509 **certs; + size_t n_certs; + size_t i; + + if (ssl_init()) { + return; + } + + if (!read_cert_file(file_name, &certs, &n_certs)) { + for (i = 0; i < n_certs; i++) { + if (SSL_CTX_add_extra_chain_cert(ctx, certs[i]) != 1) { + VLOG_ERR("SSL_CTX_add_extra_chain_cert: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + } + free(certs); + } +} + +/* Logs fingerprint of CA certificate 'cert' obtained from 'file_name'. */ +static void +log_ca_cert(const char *file_name, X509 *cert) +{ + unsigned char digest[EVP_MAX_MD_SIZE]; + unsigned int n_bytes; + struct ds fp; + char *subject; + + ds_init(&fp); + if (!X509_digest(cert, EVP_sha1(), digest, &n_bytes)) { + ds_put_cstr(&fp, ""); + } else { + unsigned int i; + for (i = 0; i < n_bytes; i++) { + if (i) { + ds_put_char(&fp, ':'); + } + ds_put_format(&fp, "%02hhx", digest[i]); + } + } + subject = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); + VLOG_INFO("Trusting CA cert from %s (%s) (fingerprint %s)", file_name, + subject ? subject : "", ds_cstr(&fp)); + free(subject); + ds_destroy(&fp); +} + +/* Sets 'file_name' as the name of the file from which to read the CA + * certificate used to verify the peer within SSL connections. If 'bootstrap' + * is false, the file must exist. If 'bootstrap' is false, then the file is + * read if it is exists; if it does not, then it will be created from the CA + * certificate received from the peer on the first SSL connection. */ +void +stream_ssl_set_ca_cert_file(const char *file_name, bool bootstrap) +{ + X509 **certs; + size_t n_certs; + struct stat s; + + if (ssl_init()) { + return; + } + + if (bootstrap && stat(file_name, &s) && errno == ENOENT) { + bootstrap_ca_cert = true; + ca_cert_file = xstrdup(file_name); + } else if (!read_cert_file(file_name, &certs, &n_certs)) { + size_t i; + + /* Set up list of CAs that the server will accept from the client. */ + for (i = 0; i < n_certs; i++) { + /* SSL_CTX_add_client_CA makes a copy of the relevant data. */ + if (SSL_CTX_add_client_CA(ctx, certs[i]) != 1) { + VLOG_ERR("failed to add client certificate %d from %s: %s", + i, file_name, + ERR_error_string(ERR_get_error(), NULL)); + } else { + log_ca_cert(file_name, certs[i]); + } + X509_free(certs[i]); + } + free(certs); + + /* Set up CAs for OpenSSL to trust in verifying the peer's + * certificate. */ + if (SSL_CTX_load_verify_locations(ctx, file_name, NULL) != 1) { + VLOG_ERR("SSL_CTX_load_verify_locations: %s", + ERR_error_string(ERR_get_error(), NULL)); + return; + } + + has_ca_cert = true; + } +} diff --cc lib/stream-tcp.c index e690e9c5,00000000..a9bcaeb4 mode 100644,000000..100644 --- a/lib/stream-tcp.c +++ b/lib/stream-tcp.c @@@ -1,143 -1,0 +1,143 @@@ +/* + * Copyright (c) 2008, 2009, 2010 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "stream.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "packets.h" +#include "socket-util.h" +#include "util.h" +#include "stream-provider.h" +#include "stream-fd.h" + +#include "vlog.h" +#define THIS_MODULE VLM_stream_tcp + +/* Active TCP. */ + +static int +new_tcp_stream(const char *name, int fd, int connect_status, + const struct sockaddr_in *remote, struct stream **streamp) +{ + struct sockaddr_in local; + socklen_t local_len = sizeof local; + int on = 1; + int retval; + + /* Get the local IP and port information */ + retval = getsockname(fd, (struct sockaddr *)&local, &local_len); + if (retval) { + memset(&local, 0, sizeof local); + } + + retval = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof on); + if (retval) { + VLOG_ERR("%s: setsockopt(TCP_NODELAY): %s", name, strerror(errno)); + close(fd); + return errno; + } + + retval = new_fd_stream(name, fd, connect_status, NULL, streamp); + if (!retval) { + struct stream *stream = *streamp; + stream_set_remote_ip(stream, remote->sin_addr.s_addr); + stream_set_remote_port(stream, remote->sin_port); + stream_set_local_ip(stream, local.sin_addr.s_addr); + stream_set_local_port(stream, local.sin_port); + } + return retval; +} + +static int +tcp_open(const char *name, char *suffix, struct stream **streamp) +{ + struct sockaddr_in sin; + int fd, error; + + error = inet_open_active(SOCK_STREAM, suffix, 0, &sin, &fd); + if (fd >= 0) { + return new_tcp_stream(name, fd, error, &sin, streamp); + } else { + VLOG_ERR("%s: connect: %s", name, strerror(error)); + return error; + } +} + +struct stream_class tcp_stream_class = { + "tcp", /* name */ + tcp_open, /* open */ + NULL, /* close */ + NULL, /* connect */ + NULL, /* recv */ + NULL, /* send */ + NULL, /* run */ + NULL, /* run_wait */ + NULL, /* wait */ +}; + +/* Passive TCP. */ + +static int ptcp_accept(int fd, const struct sockaddr *sa, size_t sa_len, + struct stream **streamp); + +static int - ptcp_open(const char *name UNUSED, char *suffix, struct pstream **pstreamp) ++ptcp_open(const char *name OVS_UNUSED, char *suffix, struct pstream **pstreamp) +{ + struct sockaddr_in sin; + char bound_name[128]; + int fd; + + fd = inet_open_passive(SOCK_STREAM, suffix, -1, &sin); + if (fd < 0) { + return -fd; + } + + sprintf(bound_name, "ptcp:%"PRIu16":"IP_FMT, + ntohs(sin.sin_port), IP_ARGS(&sin.sin_addr.s_addr)); + return new_fd_pstream(bound_name, fd, ptcp_accept, NULL, pstreamp); +} + +static int +ptcp_accept(int fd, const struct sockaddr *sa, size_t sa_len, + struct stream **streamp) +{ + const struct sockaddr_in *sin = (const struct sockaddr_in *) sa; + char name[128]; + + if (sa_len == sizeof(struct sockaddr_in) && sin->sin_family == AF_INET) { + sprintf(name, "tcp:"IP_FMT, IP_ARGS(&sin->sin_addr)); + sprintf(strchr(name, '\0'), ":%"PRIu16, ntohs(sin->sin_port)); + } else { + strcpy(name, "tcp"); + } + return new_tcp_stream(name, fd, 0, sin, streamp); +} + +struct pstream_class ptcp_pstream_class = { + "ptcp", + ptcp_open, + NULL, + NULL, + NULL +}; + diff --cc lib/stream-unix.c index 6ce7790b,00000000..33f566b3 mode 100644,000000..100644 --- a/lib/stream-unix.c +++ b/lib/stream-unix.c @@@ -1,127 -1,0 +1,128 @@@ +/* - * Copyright (c) 2008, 2009 Nicira Networks. ++ * Copyright (c) 2008, 2009, 2010 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "stream.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "packets.h" +#include "poll-loop.h" +#include "socket-util.h" +#include "util.h" +#include "stream-provider.h" +#include "stream-fd.h" + +#include "vlog.h" +#define THIS_MODULE VLM_stream_unix + +/* Active UNIX socket. */ + +/* Number of unix sockets created so far, to ensure binding path uniqueness. */ +static int n_unix_sockets; + +static int +unix_open(const char *name, char *suffix, struct stream **streamp) +{ + const char *connect_path = suffix; + char *bind_path; + int fd; + + bind_path = xasprintf("/tmp/stream-unix.%ld.%d", + (long int) getpid(), n_unix_sockets++); + fd = make_unix_socket(SOCK_STREAM, true, false, bind_path, connect_path); + if (fd < 0) { + VLOG_ERR("%s: connection to %s failed: %s", + bind_path, connect_path, strerror(-fd)); + free(bind_path); + return -fd; + } + + return new_fd_stream(name, fd, check_connection_completion(fd), + bind_path, streamp); +} + +struct stream_class unix_stream_class = { + "unix", /* name */ + unix_open, /* open */ + NULL, /* close */ + NULL, /* connect */ + NULL, /* recv */ + NULL, /* send */ + NULL, /* run */ + NULL, /* run_wait */ + NULL, /* wait */ +}; + +/* Passive UNIX socket. */ + +static int punix_accept(int fd, const struct sockaddr *sa, size_t sa_len, + struct stream **streamp); + +static int - punix_open(const char *name UNUSED, char *suffix, struct pstream **pstreamp) ++punix_open(const char *name OVS_UNUSED, char *suffix, ++ struct pstream **pstreamp) +{ + int fd, error; + + fd = make_unix_socket(SOCK_STREAM, true, true, suffix, NULL); + if (fd < 0) { + VLOG_ERR("%s: binding failed: %s", suffix, strerror(errno)); + return errno; + } + + if (listen(fd, 10) < 0) { + error = errno; + VLOG_ERR("%s: listen: %s", name, strerror(error)); + close(fd); + return error; + } + + return new_fd_pstream("punix", fd, punix_accept, + xstrdup(suffix), pstreamp); +} + +static int +punix_accept(int fd, const struct sockaddr *sa, size_t sa_len, + struct stream **streamp) +{ + const struct sockaddr_un *sun = (const struct sockaddr_un *) sa; + int name_len = get_unix_name_len(sa_len); + char name[128]; + + if (name_len > 0) { + snprintf(name, sizeof name, "unix:%.*s", name_len, sun->sun_path); + } else { + strcpy(name, "unix"); + } + return new_fd_stream(name, fd, 0, NULL, streamp); +} + +struct pstream_class punix_pstream_class = { + "punix", + punix_open, + NULL, + NULL, + NULL +}; + diff --cc lib/stream.c index dcd8da58,00000000..db6ec61b mode 100644,000000..100644 --- a/lib/stream.c +++ b/lib/stream.c @@@ -1,563 -1,0 +1,563 @@@ +/* + * Copyright (c) 2008, 2009, 2010 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "stream-provider.h" +#include +#include +#include +#include +#include +#include +#include +#include "coverage.h" +#include "dynamic-string.h" +#include "flow.h" +#include "ofp-print.h" +#include "ofpbuf.h" +#include "openflow/nicira-ext.h" +#include "openflow/openflow.h" +#include "packets.h" +#include "poll-loop.h" +#include "random.h" +#include "util.h" + +#define THIS_MODULE VLM_stream +#include "vlog.h" + +/* State of an active stream.*/ +enum stream_state { + SCS_CONNECTING, /* Underlying stream is not connected. */ + SCS_CONNECTED, /* Connection established. */ + SCS_DISCONNECTED /* Connection failed or connection closed. */ +}; + +static struct stream_class *stream_classes[] = { + &tcp_stream_class, + &unix_stream_class, +#ifdef HAVE_OPENSSL + &ssl_stream_class, +#endif +}; + +static struct pstream_class *pstream_classes[] = { + &ptcp_pstream_class, + &punix_pstream_class, +#ifdef HAVE_OPENSSL + &pssl_pstream_class, +#endif +}; + +/* Check the validity of the stream class structures. */ +static void +check_stream_classes(void) +{ +#ifndef NDEBUG + size_t i; + + for (i = 0; i < ARRAY_SIZE(stream_classes); i++) { + struct stream_class *class = stream_classes[i]; + assert(class->name != NULL); + assert(class->open != NULL); + if (class->close || class->recv || class->send || class->run + || class->run_wait || class->wait) { + assert(class->close != NULL); + assert(class->recv != NULL); + assert(class->send != NULL); + assert(class->wait != NULL); + } else { + /* This class delegates to another one. */ + } + } + + for (i = 0; i < ARRAY_SIZE(pstream_classes); i++) { + struct pstream_class *class = pstream_classes[i]; + assert(class->name != NULL); + assert(class->listen != NULL); + if (class->close || class->accept || class->wait) { + assert(class->close != NULL); + assert(class->accept != NULL); + assert(class->wait != NULL); + } else { + /* This class delegates to another one. */ + } + } +#endif +} + +/* Prints information on active (if 'active') and passive (if 'passive') + * connection methods supported by the stream. */ +void +stream_usage(const char *name, bool active, bool passive, - bool bootstrap UNUSED) ++ bool bootstrap OVS_UNUSED) +{ + /* Really this should be implemented via callbacks into the stream + * providers, but that seems too heavy-weight to bother with at the + * moment. */ + + printf("\n"); + if (active) { + printf("Active %s connection methods:\n", name); + printf(" tcp:IP:PORT " + "PORT at remote IP\n"); +#ifdef HAVE_OPENSSL + printf(" ssl:IP:PORT " + "SSL PORT at remote IP\n"); +#endif + printf(" unix:FILE " + "Unix domain socket named FILE\n"); + } + + if (passive) { + printf("Passive %s connection methods:\n", name); + printf(" ptcp:PORT[:IP] " + "listen to TCP PORT on IP\n"); +#ifdef HAVE_OPENSSL + printf(" pssl:PORT[:IP] " + "listen for SSL on PORT on IP\n"); +#endif + printf(" punix:FILE " + "listen on Unix domain socket FILE\n"); + } + +#ifdef HAVE_OPENSSL + printf("PKI configuration (required to use SSL):\n" + " -p, --private-key=FILE file with private key\n" + " -c, --certificate=FILE file with certificate for private key\n" + " -C, --ca-cert=FILE file with peer CA certificate\n"); + if (bootstrap) { + printf(" --bootstrap-ca-cert=FILE file with peer CA certificate " + "to read or create\n"); + } +#endif +} + +/* Attempts to connect a stream to a remote peer. 'name' is a connection name + * in the form "TYPE:ARGS", where TYPE is an active stream class's name and + * ARGS are stream class-specific. + * + * Returns 0 if successful, otherwise a positive errno value. If successful, + * stores a pointer to the new connection in '*streamp', otherwise a null + * pointer. */ +int +stream_open(const char *name, struct stream **streamp) +{ + size_t prefix_len; + size_t i; + + COVERAGE_INC(stream_open); + check_stream_classes(); + + *streamp = NULL; + prefix_len = strcspn(name, ":"); + if (prefix_len == strlen(name)) { + return EAFNOSUPPORT; + } + for (i = 0; i < ARRAY_SIZE(stream_classes); i++) { + struct stream_class *class = stream_classes[i]; + if (strlen(class->name) == prefix_len + && !memcmp(class->name, name, prefix_len)) { + struct stream *stream; + char *suffix_copy = xstrdup(name + prefix_len + 1); + int retval = class->open(name, suffix_copy, &stream); + free(suffix_copy); + if (!retval) { + assert(stream->state != SCS_CONNECTING + || stream->class->connect); + *streamp = stream; + } + return retval; + } + } + return EAFNOSUPPORT; +} + +int +stream_open_block(const char *name, struct stream **streamp) +{ + struct stream *stream; + int error; + + error = stream_open(name, &stream); + while (error == EAGAIN) { + stream_run(stream); + stream_run_wait(stream); + stream_connect_wait(stream); + poll_block(); + error = stream_connect(stream); + assert(error != EINPROGRESS); + } + if (error) { + stream_close(stream); + *streamp = NULL; + } else { + *streamp = stream; + } + return error; +} + +/* Closes 'stream'. */ +void +stream_close(struct stream *stream) +{ + if (stream != NULL) { + char *name = stream->name; + (stream->class->close)(stream); + free(name); + } +} + +/* Returns the name of 'stream', that is, the string passed to + * stream_open(). */ +const char * +stream_get_name(const struct stream *stream) +{ + return stream ? stream->name : "(null)"; +} + +/* Returns the IP address of the peer, or 0 if the peer is not connected over + * an IP-based protocol or if its IP address is not yet known. */ +uint32_t +stream_get_remote_ip(const struct stream *stream) +{ + return stream->remote_ip; +} + +/* Returns the transport port of the peer, or 0 if the connection does not + * contain a port or if the port is not yet known. */ +uint16_t +stream_get_remote_port(const struct stream *stream) +{ + return stream->remote_port; +} + +/* Returns the IP address used to connect to the peer, or 0 if the connection + * is not an IP-based protocol or if its IP address is not yet known. */ +uint32_t +stream_get_local_ip(const struct stream *stream) +{ + return stream->local_ip; +} + +/* Returns the transport port used to connect to the peer, or 0 if the + * connection does not contain a port or if the port is not yet known. */ +uint16_t +stream_get_local_port(const struct stream *stream) +{ + return stream->local_port; +} + +static void +scs_connecting(struct stream *stream) +{ + int retval = (stream->class->connect)(stream); + assert(retval != EINPROGRESS); + if (!retval) { + stream->state = SCS_CONNECTED; + } else if (retval != EAGAIN) { + stream->state = SCS_DISCONNECTED; + stream->error = retval; + } +} + +/* Tries to complete the connection on 'stream', which must be an active + * stream. If 'stream''s connection is complete, returns 0 if the connection + * was successful or a positive errno value if it failed. If the + * connection is still in progress, returns EAGAIN. */ +int +stream_connect(struct stream *stream) +{ + enum stream_state last_state; + + do { + last_state = stream->state; + switch (stream->state) { + case SCS_CONNECTING: + scs_connecting(stream); + break; + + case SCS_CONNECTED: + return 0; + + case SCS_DISCONNECTED: + return stream->error; + + default: + NOT_REACHED(); + } + } while (stream->state != last_state); + + return EAGAIN; +} + +/* Tries to receive up to 'n' bytes from 'stream' into 'buffer', and returns: + * + * - If successful, the number of bytes received (between 1 and 'n'). + * + * - On error, a negative errno value. + * + * - 0, if the connection has been closed in the normal fashion, or if 'n' + * is zero. + * + * The recv function will not block waiting for a packet to arrive. If no + * data have been received, it returns -EAGAIN immediately. */ +int +stream_recv(struct stream *stream, void *buffer, size_t n) +{ + int retval = stream_connect(stream); + return (retval ? -retval + : n == 0 ? 0 + : (stream->class->recv)(stream, buffer, n)); +} + +/* Tries to send up to 'n' bytes of 'buffer' on 'stream', and returns: + * + * - If successful, the number of bytes sent (between 1 and 'n'). 0 is + * only a valid return value if 'n' is 0. + * + * - On error, a negative errno value. + * + * The send function will not block. If no bytes can be immediately accepted + * for transmission, it returns -EAGAIN immediately. */ +int +stream_send(struct stream *stream, const void *buffer, size_t n) +{ + int retval = stream_connect(stream); + return (retval ? -retval + : n == 0 ? 0 + : (stream->class->send)(stream, buffer, n)); +} + +/* Allows 'stream' to perform maintenance activities, such as flushing + * output buffers. */ +void +stream_run(struct stream *stream) +{ + if (stream->class->run) { + (stream->class->run)(stream); + } +} + +/* Arranges for the poll loop to wake up when 'stream' needs to perform + * maintenance activities. */ +void +stream_run_wait(struct stream *stream) +{ + if (stream->class->run_wait) { + (stream->class->run_wait)(stream); + } +} + +/* Arranges for the poll loop to wake up when 'stream' is ready to take an + * action of the given 'type'. */ +void +stream_wait(struct stream *stream, enum stream_wait_type wait) +{ + assert(wait == STREAM_CONNECT || wait == STREAM_RECV + || wait == STREAM_SEND); + + switch (stream->state) { + case SCS_CONNECTING: + wait = STREAM_CONNECT; + break; + + case SCS_DISCONNECTED: + poll_immediate_wake(); + return; + } + (stream->class->wait)(stream, wait); +} + +void +stream_connect_wait(struct stream *stream) +{ + stream_wait(stream, STREAM_CONNECT); +} + +void +stream_recv_wait(struct stream *stream) +{ + stream_wait(stream, STREAM_RECV); +} + +void +stream_send_wait(struct stream *stream) +{ + stream_wait(stream, STREAM_SEND); +} + +/* Attempts to start listening for remote stream connections. 'name' is a + * connection name in the form "TYPE:ARGS", where TYPE is an passive stream + * class's name and ARGS are stream class-specific. + * + * Returns 0 if successful, otherwise a positive errno value. If successful, + * stores a pointer to the new connection in '*pstreamp', otherwise a null + * pointer. */ +int +pstream_open(const char *name, struct pstream **pstreamp) +{ + size_t prefix_len; + size_t i; + + check_stream_classes(); + + *pstreamp = NULL; + prefix_len = strcspn(name, ":"); + if (prefix_len == strlen(name)) { + return EAFNOSUPPORT; + } + for (i = 0; i < ARRAY_SIZE(pstream_classes); i++) { + struct pstream_class *class = pstream_classes[i]; + if (strlen(class->name) == prefix_len + && !memcmp(class->name, name, prefix_len)) { + char *suffix_copy = xstrdup(name + prefix_len + 1); + int retval = class->listen(name, suffix_copy, pstreamp); + free(suffix_copy); + if (retval) { + *pstreamp = NULL; + } + return retval; + } + } + return EAFNOSUPPORT; +} + +/* Returns the name that was used to open 'pstream'. The caller must not + * modify or free the name. */ +const char * +pstream_get_name(const struct pstream *pstream) +{ + return pstream->name; +} + +/* Closes 'pstream'. */ +void +pstream_close(struct pstream *pstream) +{ + if (pstream != NULL) { + char *name = pstream->name; + (pstream->class->close)(pstream); + free(name); + } +} + +/* Tries to accept a new connection on 'pstream'. If successful, stores the + * new connection in '*new_stream' and returns 0. Otherwise, returns a + * positive errno value. + * + * pstream_accept() will not block waiting for a connection. If no connection + * is ready to be accepted, it returns EAGAIN immediately. */ +int +pstream_accept(struct pstream *pstream, struct stream **new_stream) +{ + int retval = (pstream->class->accept)(pstream, new_stream); + if (retval) { + *new_stream = NULL; + } else { + assert((*new_stream)->state != SCS_CONNECTING + || (*new_stream)->class->connect); + } + return retval; +} + +/* Tries to accept a new connection on 'pstream'. If successful, stores the + * new connection in '*new_stream' and returns 0. Otherwise, returns a + * positive errno value. + * + * pstream_accept_block() blocks until a connection is ready or until an error + * occurs. It will not return EAGAIN. */ +int +pstream_accept_block(struct pstream *pstream, struct stream **new_stream) +{ + int error; + + while ((error = pstream_accept(pstream, new_stream)) == EAGAIN) { + pstream_wait(pstream); + poll_block(); + } + if (error) { + *new_stream = NULL; + } + return error; +} + +void +pstream_wait(struct pstream *pstream) +{ + (pstream->class->wait)(pstream); +} + +/* Initializes 'stream' as a new stream named 'name', implemented via 'class'. + * The initial connection status, supplied as 'connect_status', is interpreted + * as follows: + * + * - 0: 'stream' is connected. Its 'send' and 'recv' functions may be + * called in the normal fashion. + * + * - EAGAIN: 'stream' is trying to complete a connection. Its 'connect' + * function should be called to complete the connection. + * + * - Other positive errno values indicate that the connection failed with + * the specified error. + * + * After calling this function, stream_close() must be used to destroy + * 'stream', otherwise resources will be leaked. + * + * The caller retains ownership of 'name'. */ +void +stream_init(struct stream *stream, struct stream_class *class, + int connect_status, const char *name) +{ + stream->class = class; + stream->state = (connect_status == EAGAIN ? SCS_CONNECTING + : !connect_status ? SCS_CONNECTED + : SCS_DISCONNECTED); + stream->error = connect_status; + stream->name = xstrdup(name); + assert(stream->state != SCS_CONNECTING || class->connect); +} + +void +stream_set_remote_ip(struct stream *stream, uint32_t ip) +{ + stream->remote_ip = ip; +} + +void +stream_set_remote_port(struct stream *stream, uint16_t port) +{ + stream->remote_port = port; +} + +void +stream_set_local_ip(struct stream *stream, uint32_t ip) +{ + stream->local_ip = ip; +} + +void +stream_set_local_port(struct stream *stream, uint16_t port) +{ + stream->local_port = port; +} + +void +pstream_init(struct pstream *pstream, struct pstream_class *class, + const char *name) +{ + pstream->class = class; + pstream->name = xstrdup(name); +} diff --cc lib/timeval.h index 5ba903e3,e9401626..89abe803 --- a/lib/timeval.h +++ b/lib/timeval.h @@@ -1,5 -1,5 +1,5 @@@ /* -- * Copyright (c) 2008, 2009 Nicira Networks. ++ * Copyright (c) 2008, 2009, 2010 Nicira Networks. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@@ -51,6 -53,8 +55,10 @@@ void time_timeval(struct timeval *) void time_alarm(unsigned int secs); int time_poll(struct pollfd *, int n_pollfds, int timeout); +long long int timeval_to_msec(const struct timeval *); + + #ifdef __cplusplus + } + #endif + #endif /* timeval.h */ diff --cc lib/unixctl.c index e648cc86,43a174cc..f710ffd6 --- a/lib/unixctl.c +++ b/lib/unixctl.c @@@ -77,8 -76,7 +77,8 @@@ static struct vlog_rate_limit rl = VLOG static struct shash commands = SHASH_INITIALIZER(&commands); static void - unixctl_help(struct unixctl_conn *conn, const char *args UNUSED, - void *aux UNUSED) -unixctl_help(struct unixctl_conn *conn, const char *args OVS_UNUSED) ++unixctl_help(struct unixctl_conn *conn, const char *args OVS_UNUSED, ++ void *aux OVS_UNUSED) { struct ds ds = DS_EMPTY_INITIALIZER; struct shash_node *node; diff --cc lib/util.c index e9284809,f766d59e..8f1892ed --- a/lib/util.c +++ b/lib/util.c @@@ -1,5 -1,5 +1,5 @@@ /* -- * Copyright (c) 2008, 2009 Nicira Networks. ++ * Copyright (c) 2008, 2009, 2010 Nicira Networks. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@@ -302,90 -294,3 +302,90 @@@ str_to_ullong(const char *s, int base, { return str_to_llong(s, base, (long long *) ull); } + +/* Converts floating-point string 's' into a double. If successful, stores + * the double in '*d' and returns true; on failure, stores 0 in '*d' and + * returns false. + * + * Underflow (e.g. "1e-9999") is not considered an error, but overflow + * (e.g. "1e9999)" is. */ +bool +str_to_double(const char *s, double *d) +{ + int save_errno = errno; + char *tail; + errno = 0; + *d = strtod(s, &tail); + if (errno == EINVAL || (errno == ERANGE && *d != 0) + || tail == s || *tail != '\0') { + errno = save_errno; + *d = 0; + return false; + } else { + errno = save_errno; + return true; + } +} + +/* Returns the value of 'c' as a hexadecimal digit. */ +int +hexit_value(int c) +{ + switch (c) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + return c - '0'; + + case 'a': case 'A': + return 0xa; + + case 'b': case 'B': + return 0xb; + + case 'c': case 'C': + return 0xc; + + case 'd': case 'D': + return 0xd; + + case 'e': case 'E': + return 0xe; + + case 'f': case 'F': + return 0xf; + } + + NOT_REACHED(); +} + +/* Returns the directory name portion of 'file_name' as a malloc()'d string, + * similar to the POSIX dirname() function but thread-safe. */ +char * +dir_name(const char *file_name) +{ + size_t len = strlen(file_name); + while (len > 0 && file_name[len - 1] == '/') { + len--; + } + while (len > 0 && file_name[len - 1] != '/') { + len--; + } + while (len > 0 && file_name[len - 1] == '/') { + len--; + } + if (!len) { + return xstrdup((file_name[0] == '/' + && file_name[1] == '/' + && file_name[2] != '/') ? "//" + : file_name[0] == '/' ? "/" + : "."); + } else { + return xmemdup0(file_name, len); + } +} + +/* Pass a value to this function if it is marked with + * __attribute__((warn_unused_result)) and you genuinely want to ignore + * its return value. (Note that every scalar type can be implicitly + * converted to bool.) */ - void ignore(bool x UNUSED) { } ++void ignore(bool x OVS_UNUSED) { } diff --cc lib/util.h index 0101bf7b,962bad2f..562f7e0e --- a/lib/util.h +++ b/lib/util.h @@@ -1,5 -1,5 +1,5 @@@ /* -- * Copyright (c) 2008, 2009 Nicira Networks. ++ * Copyright (c) 2008, 2009, 2010 Nicira Networks. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@@ -121,14 -120,6 +121,14 @@@ bool str_to_uint(const char *, int base bool str_to_ulong(const char *, int base, unsigned long *); bool str_to_ullong(const char *, int base, unsigned long long *); +bool str_to_double(const char *, double *); + +int hexit_value(int c); + +char *dir_name(const char *file_name); + - void ignore(bool x UNUSED); ++void ignore(bool x OVS_UNUSED); + #ifdef __cplusplus } #endif diff --cc lib/vconn-stream.c index 594eded5,243d381f..99eb21a8 --- a/lib/vconn-stream.c +++ b/lib/vconn-stream.c @@@ -50,50 -52,32 +50,50 @@@ static struct vconn_class stream_vconn_ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 25); -static void stream_clear_txbuf(struct stream_vconn *); -static void maybe_unlink_and_free(char *path); +static void vconn_stream_clear_txbuf(struct vconn_stream *); +static int count_fields(const char *); -/* Creates a new vconn named 'name' that will send and receive data on 'fd' and - * stores a pointer to the vconn in '*vconnp'. Initial connection status - * 'connect_status' is interpreted as described for vconn_init(). - * - * When '*vconnp' is closed, then 'unlink_path' (if nonnull) will be passed to - * fatal_signal_unlink_file_now() and then freed with free(). - * - * Returns 0 if successful, otherwise a positive errno value. (The current - * implementation never fails.) */ -int -new_stream_vconn(const char *name, int fd, int connect_status, - char *unlink_path, struct vconn **vconnp) +static struct vconn * +vconn_stream_new(struct stream *stream, int connect_status) { - struct stream_vconn *s; + struct vconn_stream *s; s = xmalloc(sizeof *s); - vconn_init(&s->vconn, &stream_vconn_class, connect_status, name); - s->fd = fd; + vconn_init(&s->vconn, &stream_vconn_class, connect_status, + stream_get_name(stream)); + s->stream = stream; s->txbuf = NULL; - s->tx_waiter = NULL; s->rxbuf = NULL; - s->unlink_path = unlink_path; - *vconnp = &s->vconn; + return &s->vconn; +} + +/* Creates a new vconn that will send and receive data on a stream named 'name' + * and stores a pointer to the vconn in '*vconnp'. + * + * Returns 0 if successful, otherwise a positive errno value. */ +static int - vconn_stream_open(const char *name_, char *suffix UNUSED, ++vconn_stream_open(const char *name_, char *suffix OVS_UNUSED, + struct vconn **vconnp) +{ + struct stream *stream; + char *name; + int error; + + if (!strncmp(name_, "tcp:", 4) && count_fields(name_) < 3) { + name = xasprintf("%s:%d", name_, OFP_TCP_PORT); + } else if (!strncmp(name_, "ssl:", 4) && count_fields(name_) < 3) { + name = xasprintf("%s:%d", name_, OFP_SSL_PORT); + } else { + name = xstrdup(name_); + } + error = stream_open(name, &stream); + free(name); + + if (error && error != EAGAIN) { + return error; + } + + *vconnp = vconn_stream_new(stream, error); return 0; } @@@ -297,31 -292,17 +297,31 @@@ pvconn_pstream_cast(struct pvconn *pvco * * Returns 0 if successful, otherwise a positive errno value. (The current * implementation never fails.) */ -int -new_pstream_pvconn(const char *name, int fd, - int (*accept_cb)(int fd, const struct sockaddr *sa, - size_t sa_len, struct vconn **vconnp), - char *unlink_path, struct pvconn **pvconnp) +static int - pvconn_pstream_listen(const char *name_, char *suffix UNUSED, ++pvconn_pstream_listen(const char *name_, char *suffix OVS_UNUSED, + struct pvconn **pvconnp) { - struct pstream_pvconn *ps = xmalloc(sizeof *ps); - pvconn_init(&ps->pvconn, &pstream_pvconn_class, name); - ps->fd = fd; - ps->accept_cb = accept_cb; - ps->unlink_path = unlink_path; + struct pvconn_pstream *ps; + struct pstream *pstream; + char *name; + int error; + + if (!strncmp(name_, "ptcp:", 5) && count_fields(name_) < 2) { + name = xasprintf("%s:%d", name_, OFP_TCP_PORT); + } else if (!strncmp(name_, "pssl:", 5) && count_fields(name_) < 2) { + name = xasprintf("%s:%d", name_, OFP_SSL_PORT); + } else { + name = xstrdup(name_); + } + error = pstream_open(name, &pstream); + free(name); + if (error) { + return error; + } + + ps = xmalloc(sizeof *ps); + pvconn_init(&ps->pvconn, &pstream_pvconn_class, name_); + ps->pstream = pstream; *pvconnp = &ps->pvconn; return 0; } diff --cc lib/vlog.c index b2d0c069,0fab00f1..b534d196 --- a/lib/vlog.c +++ b/lib/vlog.c @@@ -385,7 -385,7 +385,8 @@@ vlog_set_verbosity(const char *arg } static void - vlog_unixctl_set(struct unixctl_conn *conn, const char *args, void *aux UNUSED) -vlog_unixctl_set(struct unixctl_conn *conn, const char *args) ++vlog_unixctl_set(struct unixctl_conn *conn, ++ const char *args, void *aux OVS_UNUSED) { char *msg = vlog_set_levels_from_string(args); unixctl_command_reply(conn, msg ? 501 : 202, msg); @@@ -393,8 -393,7 +394,8 @@@ } static void -vlog_unixctl_list(struct unixctl_conn *conn, const char *args OVS_UNUSED) +vlog_unixctl_list(struct unixctl_conn *conn, - const char *args UNUSED, void *aux UNUSED) ++ const char *args OVS_UNUSED, void *aux OVS_UNUSED) { char *msg = vlog_get_levels(); unixctl_command_reply(conn, 200, msg); @@@ -402,8 -401,7 +403,8 @@@ } static void -vlog_unixctl_reopen(struct unixctl_conn *conn, const char *args OVS_UNUSED) +vlog_unixctl_reopen(struct unixctl_conn *conn, - const char *args UNUSED, void *aux UNUSED) ++ const char *args OVS_UNUSED, void *aux OVS_UNUSED) { if (log_file_name) { int error = vlog_reopen_log_file(); diff --cc ovsdb/execution.c index 3e3d3563,00000000..b984b1b5 mode 100644,000000..100644 --- a/ovsdb/execution.c +++ b/ovsdb/execution.c @@@ -1,706 -1,0 +1,706 @@@ +/* Copyright (c) 2009, 2010 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#include "column.h" +#include "condition.h" +#include "file.h" +#include "json.h" +#include "mutation.h" +#include "ovsdb-data.h" +#include "ovsdb-error.h" +#include "ovsdb-parser.h" +#include "ovsdb.h" +#include "query.h" +#include "row.h" +#include "table.h" +#include "timeval.h" +#include "transaction.h" + +struct ovsdb_execution { + struct ovsdb *db; + struct ovsdb_txn *txn; + struct ovsdb_symbol_table *symtab; + bool durable; + + /* Triggers. */ + long long int elapsed_msec; + long long int timeout_msec; +}; + +typedef struct ovsdb_error *ovsdb_operation_executor(struct ovsdb_execution *, + struct ovsdb_parser *, + struct json *result); + +static ovsdb_operation_executor ovsdb_execute_insert; +static ovsdb_operation_executor ovsdb_execute_select; +static ovsdb_operation_executor ovsdb_execute_update; +static ovsdb_operation_executor ovsdb_execute_mutate; +static ovsdb_operation_executor ovsdb_execute_delete; +static ovsdb_operation_executor ovsdb_execute_wait; +static ovsdb_operation_executor ovsdb_execute_commit; +static ovsdb_operation_executor ovsdb_execute_abort; +static ovsdb_operation_executor ovsdb_execute_comment; + +static ovsdb_operation_executor * +lookup_executor(const char *name) +{ + struct ovsdb_operation { + const char *name; + ovsdb_operation_executor *executor; + }; + + static const struct ovsdb_operation operations[] = { + { "insert", ovsdb_execute_insert }, + { "select", ovsdb_execute_select }, + { "update", ovsdb_execute_update }, + { "mutate", ovsdb_execute_mutate }, + { "delete", ovsdb_execute_delete }, + { "wait", ovsdb_execute_wait }, + { "commit", ovsdb_execute_commit }, + { "abort", ovsdb_execute_abort }, + { "comment", ovsdb_execute_comment }, + }; + + size_t i; + + for (i = 0; i < ARRAY_SIZE(operations); i++) { + const struct ovsdb_operation *c = &operations[i]; + if (!strcmp(c->name, name)) { + return c->executor; + } + } + return NULL; +} + +struct json * +ovsdb_execute(struct ovsdb *db, const struct json *params, + long long int elapsed_msec, long long int *timeout_msec) +{ + struct ovsdb_execution x; + struct ovsdb_error *error; + struct json *results; + size_t n_operations; + size_t i; + + if (params->type != JSON_ARRAY + || !params->u.array.n + || params->u.array.elems[0]->type != JSON_STRING + || strcmp(params->u.array.elems[0]->u.string, db->schema->name)) { + struct ovsdb_error *error; + + if (params->type != JSON_ARRAY) { + error = ovsdb_syntax_error(params, NULL, "array expected"); + } else { + error = ovsdb_syntax_error(params, NULL, "database name expected " + "as first parameter"); + } + + results = ovsdb_error_to_json(error); + ovsdb_error_destroy(error); + return results; + } + + x.db = db; + x.txn = ovsdb_txn_create(db); + x.symtab = ovsdb_symbol_table_create(); + x.durable = false; + x.elapsed_msec = elapsed_msec; + x.timeout_msec = LLONG_MAX; + results = NULL; + + results = json_array_create_empty(); + n_operations = params->u.array.n - 1; + error = NULL; + for (i = 1; i <= n_operations; i++) { + struct json *operation = params->u.array.elems[i]; + struct ovsdb_error *parse_error; + struct ovsdb_parser parser; + struct json *result; + const struct json *op; + + /* Parse and execute operation. */ + ovsdb_parser_init(&parser, operation, + "ovsdb operation %zu of %zu", i + 1, n_operations); + op = ovsdb_parser_member(&parser, "op", OP_ID); + result = json_object_create(); + if (op) { + const char *op_name = json_string(op); + ovsdb_operation_executor *executor = lookup_executor(op_name); + if (executor) { + error = executor(&x, &parser, result); + } else { + ovsdb_parser_raise_error(&parser, "No operation \"%s\"", + op_name); + } + } else { + assert(ovsdb_parser_has_error(&parser)); + } + + /* A parse error overrides any other error. + * An error overrides any other result. */ + parse_error = ovsdb_parser_finish(&parser); + if (parse_error) { + ovsdb_error_destroy(error); + error = parse_error; + } + if (error) { + json_destroy(result); + result = ovsdb_error_to_json(error); + } + if (error && !strcmp(ovsdb_error_get_tag(error), "not supported") + && timeout_msec) { + ovsdb_txn_abort(x.txn); + *timeout_msec = x.timeout_msec; + + json_destroy(result); + json_destroy(results); + results = NULL; + goto exit; + } + + /* Add result to array. */ + json_array_add(results, result); + if (error) { + break; + } + } + + if (!error) { + error = ovsdb_txn_commit(x.txn, x.durable); + if (error) { + json_array_add(results, ovsdb_error_to_json(error)); + } + } else { + ovsdb_txn_abort(x.txn); + } + + while (json_array(results)->n < n_operations) { + json_array_add(results, json_null_create()); + } + +exit: + ovsdb_error_destroy(error); + ovsdb_symbol_table_destroy(x.symtab); + + return results; +} + +struct ovsdb_error * +ovsdb_execute_commit(struct ovsdb_execution *x, struct ovsdb_parser *parser, - struct json *result UNUSED) ++ struct json *result OVS_UNUSED) +{ + const struct json *durable; + + durable = ovsdb_parser_member(parser, "durable", OP_BOOLEAN); + if (durable && json_boolean(durable)) { + x->durable = true; + } + return NULL; +} + +static struct ovsdb_error * - ovsdb_execute_abort(struct ovsdb_execution *x UNUSED, - struct ovsdb_parser *parser UNUSED, - struct json *result UNUSED) ++ovsdb_execute_abort(struct ovsdb_execution *x OVS_UNUSED, ++ struct ovsdb_parser *parser OVS_UNUSED, ++ struct json *result OVS_UNUSED) +{ + return ovsdb_error("aborted", "aborted by request"); +} + +static struct ovsdb_table * +parse_table(struct ovsdb_execution *x, + struct ovsdb_parser *parser, const char *member) +{ + struct ovsdb_table *table; + const char *table_name; + const struct json *json; + + json = ovsdb_parser_member(parser, member, OP_ID); + if (!json) { + return NULL; + } + table_name = json_string(json); + + table = shash_find_data(&x->db->tables, table_name); + if (!table) { + ovsdb_parser_raise_error(parser, "No table named %s.", table_name); + } + return table; +} + +static WARN_UNUSED_RESULT struct ovsdb_error * +parse_row(struct ovsdb_parser *parser, const char *member, + const struct ovsdb_table *table, + struct ovsdb_symbol_table *symtab, + struct ovsdb_row **rowp, struct ovsdb_column_set *columns) +{ + struct ovsdb_error *error; + const struct json *json; + struct ovsdb_row *row; + + *rowp = NULL; + + if (!table) { + return OVSDB_BUG("null table"); + } + json = ovsdb_parser_member(parser, member, OP_OBJECT); + if (!json) { + return OVSDB_BUG("null row member"); + } + + row = ovsdb_row_create(table); + error = ovsdb_row_from_json(row, json, symtab, columns); + if (error) { + ovsdb_row_destroy(row); + return error; + } else { + *rowp = row; + return NULL; + } +} + +struct ovsdb_error * +ovsdb_execute_insert(struct ovsdb_execution *x, struct ovsdb_parser *parser, + struct json *result) +{ + struct ovsdb_table *table; + struct ovsdb_row *row = NULL; + const struct json *uuid_name; + struct ovsdb_error *error; + struct uuid row_uuid; + + table = parse_table(x, parser, "table"); + uuid_name = ovsdb_parser_member(parser, "uuid-name", OP_ID | OP_OPTIONAL); + error = ovsdb_parser_get_error(parser); + + if (uuid_name) { + struct ovsdb_symbol *symbol; + + symbol = ovsdb_symbol_table_insert(x->symtab, json_string(uuid_name)); + if (symbol->used) { + return ovsdb_syntax_error(uuid_name, "duplicate uuid-name", + "This \"uuid-name\" appeared on an " + "earlier \"insert\" operation."); + } + row_uuid = symbol->uuid; + symbol->used = true; + } else { + uuid_generate(&row_uuid); + } + + if (!error) { + error = parse_row(parser, "row", table, x->symtab, &row, NULL); + } + if (!error) { + /* Check constraints for columns not included in "row", in case the + * default values do not satisfy the constraints. We could check only + * the columns that have their default values by supplying an + * ovsdb_column_set to parse_row() above, but I suspect that this is + * cheaper. */ + const struct shash_node *node; + + SHASH_FOR_EACH (node, &table->schema->columns) { + const struct ovsdb_column *column = node->data; + const struct ovsdb_datum *datum = &row->fields[column->index]; + + /* If there are 0 keys or pairs, there's nothing to check. + * If there is 1, it might be a default value. + * If there are more, it can't be a default value, so the value has + * already been checked. */ + if (datum->n == 1) { + error = ovsdb_datum_check_constraints(datum, &column->type); + if (error) { + ovsdb_row_destroy(row); + break; + } + } + } + } + if (!error) { + *ovsdb_row_get_uuid_rw(row) = row_uuid; + ovsdb_txn_row_insert(x->txn, row); + json_object_put(result, "uuid", + ovsdb_datum_to_json(&row->fields[OVSDB_COL_UUID], + &ovsdb_type_uuid)); + } + return error; +} + +struct ovsdb_error * +ovsdb_execute_select(struct ovsdb_execution *x, struct ovsdb_parser *parser, + struct json *result) +{ + struct ovsdb_table *table; + const struct json *where, *columns_json, *sort_json; + struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER; + struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER; + struct ovsdb_column_set sort = OVSDB_COLUMN_SET_INITIALIZER; + struct ovsdb_error *error; + + table = parse_table(x, parser, "table"); + where = ovsdb_parser_member(parser, "where", OP_ARRAY); + columns_json = ovsdb_parser_member(parser, "columns", + OP_ARRAY | OP_OPTIONAL); + sort_json = ovsdb_parser_member(parser, "sort", OP_ARRAY | OP_OPTIONAL); + + error = ovsdb_parser_get_error(parser); + if (!error) { + error = ovsdb_condition_from_json(table->schema, where, x->symtab, + &condition); + } + if (!error) { + error = ovsdb_column_set_from_json(columns_json, table, &columns); + } + if (!error) { + error = ovsdb_column_set_from_json(sort_json, table, &sort); + } + if (!error) { + struct ovsdb_row_set rows = OVSDB_ROW_SET_INITIALIZER; + + ovsdb_query_distinct(table, &condition, &columns, &rows); + ovsdb_row_set_sort(&rows, &sort); + json_object_put(result, "rows", + ovsdb_row_set_to_json(&rows, &columns)); + + ovsdb_row_set_destroy(&rows); + } + + ovsdb_column_set_destroy(&columns); + ovsdb_column_set_destroy(&sort); + ovsdb_condition_destroy(&condition); + + return error; +} + +struct update_row_cbdata { + size_t n_matches; + struct ovsdb_txn *txn; + const struct ovsdb_row *row; + const struct ovsdb_column_set *columns; +}; + +static bool +update_row_cb(const struct ovsdb_row *row, void *ur_) +{ + struct update_row_cbdata *ur = ur_; + + ur->n_matches++; + if (!ovsdb_row_equal_columns(row, ur->row, ur->columns)) { + ovsdb_row_update_columns(ovsdb_txn_row_modify(ur->txn, row), + ur->row, ur->columns); + } + + return true; +} + +struct ovsdb_error * +ovsdb_execute_update(struct ovsdb_execution *x, struct ovsdb_parser *parser, + struct json *result) +{ + struct ovsdb_table *table; + const struct json *where; + struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER; + struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER; + struct ovsdb_row *row = NULL; + struct update_row_cbdata ur; + struct ovsdb_error *error; + + table = parse_table(x, parser, "table"); + where = ovsdb_parser_member(parser, "where", OP_ARRAY); + error = ovsdb_parser_get_error(parser); + if (!error) { + error = parse_row(parser, "row", table, x->symtab, &row, &columns); + } + if (!error) { + error = ovsdb_condition_from_json(table->schema, where, x->symtab, + &condition); + } + if (!error) { + ur.n_matches = 0; + ur.txn = x->txn; + ur.row = row; + ur.columns = &columns; + ovsdb_query(table, &condition, update_row_cb, &ur); + json_object_put(result, "count", json_integer_create(ur.n_matches)); + } + + ovsdb_row_destroy(row); + ovsdb_column_set_destroy(&columns); + ovsdb_condition_destroy(&condition); + + return error; +} + +struct mutate_row_cbdata { + size_t n_matches; + struct ovsdb_txn *txn; + const struct ovsdb_mutation_set *mutations; +}; + +static bool +mutate_row_cb(const struct ovsdb_row *row, void *mr_) +{ + struct mutate_row_cbdata *mr = mr_; + + mr->n_matches++; + ovsdb_mutation_set_execute(ovsdb_txn_row_modify(mr->txn, row), + mr->mutations); + + return true; +} + +struct ovsdb_error * +ovsdb_execute_mutate(struct ovsdb_execution *x, struct ovsdb_parser *parser, + struct json *result) +{ + struct ovsdb_table *table; + const struct json *where; + const struct json *mutations_json; + struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER; + struct ovsdb_mutation_set mutations = OVSDB_MUTATION_SET_INITIALIZER; + struct ovsdb_row *row = NULL; + struct mutate_row_cbdata mr; + struct ovsdb_error *error; + + table = parse_table(x, parser, "table"); + where = ovsdb_parser_member(parser, "where", OP_ARRAY); + mutations_json = ovsdb_parser_member(parser, "mutations", OP_ARRAY); + error = ovsdb_parser_get_error(parser); + if (!error) { + error = ovsdb_mutation_set_from_json(table->schema, mutations_json, + x->symtab, &mutations); + } + if (!error) { + error = ovsdb_condition_from_json(table->schema, where, x->symtab, + &condition); + } + if (!error) { + mr.n_matches = 0; + mr.txn = x->txn; + mr.mutations = &mutations; + ovsdb_query(table, &condition, mutate_row_cb, &mr); + json_object_put(result, "count", json_integer_create(mr.n_matches)); + } + + ovsdb_row_destroy(row); + ovsdb_mutation_set_destroy(&mutations); + ovsdb_condition_destroy(&condition); + + return error; +} + +struct delete_row_cbdata { + size_t n_matches; + const struct ovsdb_table *table; + struct ovsdb_txn *txn; +}; + +static bool +delete_row_cb(const struct ovsdb_row *row, void *dr_) +{ + struct delete_row_cbdata *dr = dr_; + + dr->n_matches++; + ovsdb_txn_row_delete(dr->txn, row); + + return true; +} + +struct ovsdb_error * +ovsdb_execute_delete(struct ovsdb_execution *x, struct ovsdb_parser *parser, + struct json *result) +{ + struct ovsdb_table *table; + const struct json *where; + struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER; + struct ovsdb_error *error; + + where = ovsdb_parser_member(parser, "where", OP_ARRAY); + table = parse_table(x, parser, "table"); + error = ovsdb_parser_get_error(parser); + if (!error) { + error = ovsdb_condition_from_json(table->schema, where, x->symtab, + &condition); + } + if (!error) { + struct delete_row_cbdata dr; + + dr.n_matches = 0; + dr.table = table; + dr.txn = x->txn; + ovsdb_query(table, &condition, delete_row_cb, &dr); + + json_object_put(result, "count", json_integer_create(dr.n_matches)); + } + + ovsdb_condition_destroy(&condition); + + return error; +} + +struct wait_auxdata { + struct ovsdb_row_hash *actual; + struct ovsdb_row_hash *expected; + bool *equal; +}; + +static bool +ovsdb_execute_wait_query_cb(const struct ovsdb_row *row, void *aux_) +{ + struct wait_auxdata *aux = aux_; + + if (ovsdb_row_hash_contains(aux->expected, row)) { + ovsdb_row_hash_insert(aux->actual, row); + return true; + } else { + /* The query row isn't in the expected result set, so the actual and + * expected results sets definitely differ and we can short-circuit the + * rest of the query. */ + *aux->equal = false; + return false; + } +} + +static struct ovsdb_error * +ovsdb_execute_wait(struct ovsdb_execution *x, struct ovsdb_parser *parser, - struct json *result UNUSED) ++ struct json *result OVS_UNUSED) +{ + struct ovsdb_table *table; + const struct json *timeout, *where, *columns_json, *until, *rows; + struct ovsdb_condition condition = OVSDB_CONDITION_INITIALIZER; + struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER; + struct ovsdb_row_hash expected = OVSDB_ROW_HASH_INITIALIZER(expected); + struct ovsdb_row_hash actual = OVSDB_ROW_HASH_INITIALIZER(actual); + struct ovsdb_error *error; + struct wait_auxdata aux; + long long int timeout_msec = 0; + size_t i; + + timeout = ovsdb_parser_member(parser, "timeout", OP_NUMBER | OP_OPTIONAL); + where = ovsdb_parser_member(parser, "where", OP_ARRAY); + columns_json = ovsdb_parser_member(parser, "columns", + OP_ARRAY | OP_OPTIONAL); + until = ovsdb_parser_member(parser, "until", OP_STRING); + rows = ovsdb_parser_member(parser, "rows", OP_ARRAY); + table = parse_table(x, parser, "table"); + error = ovsdb_parser_get_error(parser); + if (!error) { + error = ovsdb_condition_from_json(table->schema, where, x->symtab, + &condition); + } + if (!error) { + error = ovsdb_column_set_from_json(columns_json, table, &columns); + } + if (!error) { + if (timeout) { + timeout_msec = MIN(LLONG_MAX, json_real(timeout)); + if (timeout_msec < 0) { + error = ovsdb_syntax_error(timeout, NULL, + "timeout must be nonnegative"); + } else if (timeout_msec < x->timeout_msec) { + x->timeout_msec = timeout_msec; + } + } else { + timeout_msec = LLONG_MAX; + } + if (strcmp(json_string(until), "==") + && strcmp(json_string(until), "!=")) { + error = ovsdb_syntax_error(until, NULL, + "\"until\" must be \"==\" or \"!=\""); + } + } + if (!error) { + /* Parse "rows" into 'expected'. */ + ovsdb_row_hash_init(&expected, &columns); + for (i = 0; i < rows->u.array.n; i++) { + struct ovsdb_error *error; + struct ovsdb_row *row; + + row = ovsdb_row_create(table); + error = ovsdb_row_from_json(row, rows->u.array.elems[i], x->symtab, + NULL); + if (error) { + break; + } + + if (!ovsdb_row_hash_insert(&expected, row)) { + /* XXX Perhaps we should abort with an error or log a + * warning. */ + ovsdb_row_destroy(row); + } + } + } + if (!error) { + /* Execute query. */ + bool equal = true; + ovsdb_row_hash_init(&actual, &columns); + aux.actual = &actual; + aux.expected = &expected; + aux.equal = &equal; + ovsdb_query(table, &condition, ovsdb_execute_wait_query_cb, &aux); + if (equal) { + /* We know that every row in 'actual' is also in 'expected'. We + * also know that all of the rows in 'actual' are distinct and that + * all of the rows in 'expected' are distinct. Therefore, if + * 'actual' and 'expected' have the same number of rows, then they + * have the same content. */ + size_t n_actual = ovsdb_row_hash_count(&actual); + size_t n_expected = ovsdb_row_hash_count(&expected); + equal = n_actual == n_expected; + } + if (!strcmp(json_string(until), "==") != equal) { + if (timeout && x->elapsed_msec >= timeout_msec) { + if (x->elapsed_msec) { + error = ovsdb_error("timed out", + "\"wait\" timed out after %lld ms", + x->elapsed_msec); + } else { + error = ovsdb_error("timed out", "\"wait\" timed out"); + } + } else { + /* ovsdb_execute() will change this, if triggers really are + * supported. */ + error = ovsdb_error("not supported", "triggers not supported"); + } + } + } + + + ovsdb_row_hash_destroy(&expected, true); + ovsdb_row_hash_destroy(&actual, false); + ovsdb_column_set_destroy(&columns); + ovsdb_condition_destroy(&condition); + + return error; +} + +static struct ovsdb_error * +ovsdb_execute_comment(struct ovsdb_execution *x, struct ovsdb_parser *parser, - struct json *result UNUSED) ++ struct json *result OVS_UNUSED) +{ + const struct json *comment; + + comment = ovsdb_parser_member(parser, "comment", OP_STRING); + if (!comment) { + return NULL; + } + ovsdb_txn_add_comment(x->txn, json_string(comment)); + + return NULL; +} diff --cc ovsdb/jsonrpc-server.c index 26fabe52,00000000..e3984647 mode 100644,000000..100644 --- a/ovsdb/jsonrpc-server.c +++ b/ovsdb/jsonrpc-server.c @@@ -1,980 -1,0 +1,981 @@@ +/* Copyright (c) 2009, 2010 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "jsonrpc-server.h" + +#include +#include + +#include "column.h" +#include "json.h" +#include "jsonrpc.h" +#include "ovsdb-error.h" +#include "ovsdb-parser.h" +#include "ovsdb.h" +#include "reconnect.h" +#include "row.h" +#include "stream.h" +#include "table.h" +#include "timeval.h" +#include "transaction.h" +#include "trigger.h" + +#define THIS_MODULE VLM_ovsdb_jsonrpc_server +#include "vlog.h" + +struct ovsdb_jsonrpc_remote; +struct ovsdb_jsonrpc_session; + +/* Message rate-limiting. */ +struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + +/* Sessions. */ +static struct ovsdb_jsonrpc_session *ovsdb_jsonrpc_session_create( + struct ovsdb_jsonrpc_remote *, struct jsonrpc_session *); +static void ovsdb_jsonrpc_session_run_all(struct ovsdb_jsonrpc_remote *); +static void ovsdb_jsonrpc_session_wait_all(struct ovsdb_jsonrpc_remote *); +static void ovsdb_jsonrpc_session_close_all(struct ovsdb_jsonrpc_remote *); + +/* Triggers. */ +static void ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *, + struct json *id, struct json *params); +static struct ovsdb_jsonrpc_trigger *ovsdb_jsonrpc_trigger_find( + struct ovsdb_jsonrpc_session *, const struct json *id, size_t hash); +static void ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *); +static void ovsdb_jsonrpc_trigger_complete_all(struct ovsdb_jsonrpc_session *); +static void ovsdb_jsonrpc_trigger_complete_done( + struct ovsdb_jsonrpc_session *); + +/* Monitors. */ +static struct json *ovsdb_jsonrpc_monitor_create( + struct ovsdb_jsonrpc_session *, struct json *params); +static struct jsonrpc_msg *ovsdb_jsonrpc_monitor_cancel( + struct ovsdb_jsonrpc_session *, + struct json_array *params, + const struct json *request_id); +static void ovsdb_jsonrpc_monitor_remove_all(struct ovsdb_jsonrpc_session *); + +/* JSON-RPC database server. */ + +struct ovsdb_jsonrpc_server { + struct ovsdb *db; + unsigned int n_sessions, max_sessions; + struct shash remotes; /* Contains "struct ovsdb_jsonrpc_remote *"s. */ +}; + +/* A configured remote. This is either a passive stream listener plus a list + * of the currently connected sessions, or a list of exactly one active + * session. */ +struct ovsdb_jsonrpc_remote { + struct ovsdb_jsonrpc_server *server; + struct pstream *listener; /* Listener, if passive. */ + struct list sessions; /* List of "struct ovsdb_jsonrpc_session"s. */ +}; + +static void ovsdb_jsonrpc_server_add_remote(struct ovsdb_jsonrpc_server *, + const char *name); +static void ovsdb_jsonrpc_server_del_remote(struct shash_node *); + +struct ovsdb_jsonrpc_server * +ovsdb_jsonrpc_server_create(struct ovsdb *db) +{ + struct ovsdb_jsonrpc_server *server = xzalloc(sizeof *server); + server->db = db; + server->max_sessions = 64; + shash_init(&server->remotes); + return server; +} + +void +ovsdb_jsonrpc_server_destroy(struct ovsdb_jsonrpc_server *svr) +{ + struct shash_node *node, *next; + + SHASH_FOR_EACH_SAFE (node, next, &svr->remotes) { + ovsdb_jsonrpc_server_del_remote(node); + } + shash_destroy(&svr->remotes); + free(svr); +} + +/* Sets 'svr''s current set of remotes to the names in 'new_remotes'. The data + * values in 'new_remotes' are ignored. + * + * A remote is an active or passive stream connection method, e.g. "pssl:" or + * "tcp:1.2.3.4". */ +void +ovsdb_jsonrpc_server_set_remotes(struct ovsdb_jsonrpc_server *svr, + const struct shash *new_remotes) +{ + struct shash_node *node, *next; + + SHASH_FOR_EACH_SAFE (node, next, &svr->remotes) { + if (!shash_find(new_remotes, node->name)) { + ovsdb_jsonrpc_server_del_remote(node); + } + } + SHASH_FOR_EACH (node, new_remotes) { + if (!shash_find(&svr->remotes, node->name)) { + ovsdb_jsonrpc_server_add_remote(svr, node->name); + } + } +} + +static void +ovsdb_jsonrpc_server_add_remote(struct ovsdb_jsonrpc_server *svr, + const char *name) +{ + struct ovsdb_jsonrpc_remote *remote; + struct pstream *listener; + int error; + + error = pstream_open(name, &listener); + if (error && error != EAFNOSUPPORT) { + VLOG_ERR_RL(&rl, "%s: listen failed: %s", name, strerror(error)); + return; + } + + remote = xmalloc(sizeof *remote); + remote->server = svr; + remote->listener = listener; + list_init(&remote->sessions); + shash_add(&svr->remotes, name, remote); + + if (!listener) { + ovsdb_jsonrpc_session_create(remote, jsonrpc_session_open(name)); + } +} + +static void +ovsdb_jsonrpc_server_del_remote(struct shash_node *node) +{ + struct ovsdb_jsonrpc_remote *remote = node->data; + + ovsdb_jsonrpc_session_close_all(remote); + pstream_close(remote->listener); + shash_delete(&remote->server->remotes, node); + free(remote); +} + +void +ovsdb_jsonrpc_server_run(struct ovsdb_jsonrpc_server *svr) +{ + struct shash_node *node; + + SHASH_FOR_EACH (node, &svr->remotes) { + struct ovsdb_jsonrpc_remote *remote = node->data; + + if (remote->listener && svr->n_sessions < svr->max_sessions) { + struct stream *stream; + int error; + + error = pstream_accept(remote->listener, &stream); + if (!error) { + struct jsonrpc_session *js; + js = jsonrpc_session_open_unreliably(jsonrpc_open(stream)); + ovsdb_jsonrpc_session_create(remote, js); + } else if (error != EAGAIN) { + VLOG_WARN_RL(&rl, "%s: accept failed: %s", + pstream_get_name(remote->listener), + strerror(error)); + } + } + + ovsdb_jsonrpc_session_run_all(remote); + } +} + +void +ovsdb_jsonrpc_server_wait(struct ovsdb_jsonrpc_server *svr) +{ + struct shash_node *node; + + SHASH_FOR_EACH (node, &svr->remotes) { + struct ovsdb_jsonrpc_remote *remote = node->data; + + if (remote->listener && svr->n_sessions < svr->max_sessions) { + pstream_wait(remote->listener); + } + + ovsdb_jsonrpc_session_wait_all(remote); + } +} + +/* JSON-RPC database server session. */ + +struct ovsdb_jsonrpc_session { + struct ovsdb_jsonrpc_remote *remote; + struct list node; /* Element in remote's sessions list. */ + + /* Triggers. */ + struct hmap triggers; /* Hmap of "struct ovsdb_jsonrpc_trigger"s. */ + struct list completions; /* Completed triggers. */ + + /* Monitors. */ + struct hmap monitors; /* Hmap of "struct ovsdb_jsonrpc_monitor"s. */ + + /* Network connectivity. */ + struct jsonrpc_session *js; /* JSON-RPC session. */ + unsigned int js_seqno; /* Last jsonrpc_session_get_seqno() value. */ +}; + +static void ovsdb_jsonrpc_session_close(struct ovsdb_jsonrpc_session *); +static int ovsdb_jsonrpc_session_run(struct ovsdb_jsonrpc_session *); +static void ovsdb_jsonrpc_session_wait(struct ovsdb_jsonrpc_session *); +static void ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *, + struct jsonrpc_msg *); +static void ovsdb_jsonrpc_session_got_notify(struct ovsdb_jsonrpc_session *, + struct jsonrpc_msg *); + +static struct ovsdb_jsonrpc_session * +ovsdb_jsonrpc_session_create(struct ovsdb_jsonrpc_remote *remote, + struct jsonrpc_session *js) +{ + struct ovsdb_jsonrpc_session *s; + + s = xzalloc(sizeof *s); + s->remote = remote; + list_push_back(&remote->sessions, &s->node); + hmap_init(&s->triggers); + hmap_init(&s->monitors); + list_init(&s->completions); + s->js = js; + s->js_seqno = jsonrpc_session_get_seqno(js); + + remote->server->n_sessions++; + + return s; +} + +static void +ovsdb_jsonrpc_session_close(struct ovsdb_jsonrpc_session *s) +{ + ovsdb_jsonrpc_monitor_remove_all(s); + jsonrpc_session_close(s->js); + list_remove(&s->node); + s->remote->server->n_sessions--; + free(s); +} + +static int +ovsdb_jsonrpc_session_run(struct ovsdb_jsonrpc_session *s) +{ + jsonrpc_session_run(s->js); + if (s->js_seqno != jsonrpc_session_get_seqno(s->js)) { + s->js_seqno = jsonrpc_session_get_seqno(s->js); + ovsdb_jsonrpc_trigger_complete_all(s); + ovsdb_jsonrpc_monitor_remove_all(s); + } + + ovsdb_jsonrpc_trigger_complete_done(s); + + if (!jsonrpc_session_get_backlog(s->js)) { + struct jsonrpc_msg *msg = jsonrpc_session_recv(s->js); + if (msg) { + if (msg->type == JSONRPC_REQUEST) { + ovsdb_jsonrpc_session_got_request(s, msg); + } else if (msg->type == JSONRPC_NOTIFY) { + ovsdb_jsonrpc_session_got_notify(s, msg); + } else { + VLOG_WARN("%s: received unexpected %s message", + jsonrpc_session_get_name(s->js), + jsonrpc_msg_type_to_string(msg->type)); + jsonrpc_session_force_reconnect(s->js); + jsonrpc_msg_destroy(msg); + } + } + } + return jsonrpc_session_is_alive(s->js) ? 0 : ETIMEDOUT; +} + +static void +ovsdb_jsonrpc_session_run_all(struct ovsdb_jsonrpc_remote *remote) +{ + struct ovsdb_jsonrpc_session *s, *next; + + LIST_FOR_EACH_SAFE (s, next, struct ovsdb_jsonrpc_session, node, + &remote->sessions) { + int error = ovsdb_jsonrpc_session_run(s); + if (error) { + ovsdb_jsonrpc_session_close(s); + } + } +} + +static void +ovsdb_jsonrpc_session_wait(struct ovsdb_jsonrpc_session *s) +{ + jsonrpc_session_wait(s->js); + if (!jsonrpc_session_get_backlog(s->js)) { + jsonrpc_session_recv_wait(s->js); + } +} + +static void +ovsdb_jsonrpc_session_wait_all(struct ovsdb_jsonrpc_remote *remote) +{ + struct ovsdb_jsonrpc_session *s; + + LIST_FOR_EACH (s, struct ovsdb_jsonrpc_session, node, &remote->sessions) { + ovsdb_jsonrpc_session_wait(s); + } +} + +static void +ovsdb_jsonrpc_session_close_all(struct ovsdb_jsonrpc_remote *remote) +{ + struct ovsdb_jsonrpc_session *s, *next; + + LIST_FOR_EACH_SAFE (s, next, struct ovsdb_jsonrpc_session, node, + &remote->sessions) { + ovsdb_jsonrpc_session_close(s); + } +} + +static const char * +get_db_name(const struct ovsdb_jsonrpc_session *s) +{ + return s->remote->server->db->schema->name; +} + +static struct jsonrpc_msg * +ovsdb_jsonrpc_check_db_name(const struct ovsdb_jsonrpc_session *s, + const struct jsonrpc_msg *request) +{ + struct json_array *params; + const char *want_db_name; + const char *have_db_name; + struct ovsdb_error *error; + struct jsonrpc_msg *reply; + + params = json_array(request->params); + if (!params->n || params->elems[0]->type != JSON_STRING) { + error = ovsdb_syntax_error( + request->params, NULL, + "%s request params must begin with ", request->method); + goto error; + } + + want_db_name = params->elems[0]->u.string; + have_db_name = get_db_name(s); + if (strcmp(want_db_name, have_db_name)) { + error = ovsdb_syntax_error( + request->params, "unknown database", + "%s request specifies unknown database %s", + request->method, want_db_name); + goto error; + } + + return NULL; + +error: + reply = jsonrpc_create_reply(ovsdb_error_to_json(error), request->id); + ovsdb_error_destroy(error); + return reply; +} + +static struct jsonrpc_msg * +execute_transaction(struct ovsdb_jsonrpc_session *s, + struct jsonrpc_msg *request) +{ + ovsdb_jsonrpc_trigger_create(s, request->id, request->params); + request->id = NULL; + request->params = NULL; + jsonrpc_msg_destroy(request); + return NULL; +} + +static void +ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *s, + struct jsonrpc_msg *request) +{ + struct jsonrpc_msg *reply; + + if (!strcmp(request->method, "transact")) { + reply = ovsdb_jsonrpc_check_db_name(s, request); + if (!reply) { + reply = execute_transaction(s, request); + } + } else if (!strcmp(request->method, "monitor")) { + reply = ovsdb_jsonrpc_check_db_name(s, request); + if (!reply) { + reply = jsonrpc_create_reply( + ovsdb_jsonrpc_monitor_create(s, request->params), request->id); + } + } else if (!strcmp(request->method, "monitor_cancel")) { + reply = ovsdb_jsonrpc_monitor_cancel(s, json_array(request->params), + request->id); + } else if (!strcmp(request->method, "get_schema")) { + reply = ovsdb_jsonrpc_check_db_name(s, request); + if (!reply) { + reply = jsonrpc_create_reply( + ovsdb_schema_to_json(s->remote->server->db->schema), + request->id); + } + } else if (!strcmp(request->method, "list_dbs")) { + reply = jsonrpc_create_reply( + json_array_create_1(json_string_create(get_db_name(s))), + request->id); + } else if (!strcmp(request->method, "echo")) { + reply = jsonrpc_create_reply(json_clone(request->params), request->id); + } else { + reply = jsonrpc_create_error(json_string_create("unknown method"), + request->id); + } + + if (reply) { + jsonrpc_msg_destroy(request); + jsonrpc_session_send(s->js, reply); + } +} + +static void +execute_cancel(struct ovsdb_jsonrpc_session *s, struct jsonrpc_msg *request) +{ + if (json_array(request->params)->n == 1) { + struct ovsdb_jsonrpc_trigger *t; + struct json *id; + + id = request->params->u.array.elems[0]; + t = ovsdb_jsonrpc_trigger_find(s, id, json_hash(id, 0)); + if (t) { + ovsdb_jsonrpc_trigger_complete(t); + } + } +} + +static void +ovsdb_jsonrpc_session_got_notify(struct ovsdb_jsonrpc_session *s, + struct jsonrpc_msg *request) +{ + if (!strcmp(request->method, "cancel")) { + execute_cancel(s, request); + } + jsonrpc_msg_destroy(request); +} + +/* JSON-RPC database server triggers. + * + * (Every transaction is treated as a trigger even if it doesn't actually have + * any "wait" operations.) */ + +struct ovsdb_jsonrpc_trigger { + struct ovsdb_trigger trigger; + struct ovsdb_jsonrpc_session *session; + struct hmap_node hmap_node; /* In session's "triggers" hmap. */ + struct json *id; +}; + +static void +ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *s, + struct json *id, struct json *params) +{ + struct ovsdb_jsonrpc_trigger *t; + size_t hash; + + /* Check for duplicate ID. */ + hash = json_hash(id, 0); + t = ovsdb_jsonrpc_trigger_find(s, id, hash); + if (t) { + struct jsonrpc_msg *msg; + + msg = jsonrpc_create_error(json_string_create("duplicate request ID"), + id); + jsonrpc_session_send(s->js, msg); + json_destroy(id); + json_destroy(params); + return; + } + + /* Insert into trigger table. */ + t = xmalloc(sizeof *t); + ovsdb_trigger_init(s->remote->server->db, + &t->trigger, params, &s->completions, + time_msec()); + t->session = s; + t->id = id; + hmap_insert(&s->triggers, &t->hmap_node, hash); + + /* Complete early if possible. */ + if (ovsdb_trigger_is_complete(&t->trigger)) { + ovsdb_jsonrpc_trigger_complete(t); + } +} + +static struct ovsdb_jsonrpc_trigger * +ovsdb_jsonrpc_trigger_find(struct ovsdb_jsonrpc_session *s, + const struct json *id, size_t hash) +{ + struct ovsdb_jsonrpc_trigger *t; + + HMAP_FOR_EACH_WITH_HASH (t, struct ovsdb_jsonrpc_trigger, hmap_node, hash, + &s->triggers) { + if (json_equal(t->id, id)) { + return t; + } + } + + return NULL; +} + +static void +ovsdb_jsonrpc_trigger_complete(struct ovsdb_jsonrpc_trigger *t) +{ + struct ovsdb_jsonrpc_session *s = t->session; + + if (jsonrpc_session_is_connected(s->js)) { + struct jsonrpc_msg *reply; + struct json *result; + + result = ovsdb_trigger_steal_result(&t->trigger); + if (result) { + reply = jsonrpc_create_reply(result, t->id); + } else { + reply = jsonrpc_create_error(json_string_create("canceled"), + t->id); + } + jsonrpc_session_send(s->js, reply); + } + + json_destroy(t->id); + ovsdb_trigger_destroy(&t->trigger); + hmap_remove(&s->triggers, &t->hmap_node); + free(t); +} + +static void +ovsdb_jsonrpc_trigger_complete_all(struct ovsdb_jsonrpc_session *s) +{ + struct ovsdb_jsonrpc_trigger *t, *next; + HMAP_FOR_EACH_SAFE (t, next, struct ovsdb_jsonrpc_trigger, hmap_node, + &s->triggers) { + ovsdb_jsonrpc_trigger_complete(t); + } +} + +static void +ovsdb_jsonrpc_trigger_complete_done(struct ovsdb_jsonrpc_session *s) +{ + while (!list_is_empty(&s->completions)) { + struct ovsdb_jsonrpc_trigger *t + = CONTAINER_OF(s->completions.next, + struct ovsdb_jsonrpc_trigger, trigger.node); + ovsdb_jsonrpc_trigger_complete(t); + } +} + +/* JSON-RPC database table monitors. */ + +enum ovsdb_jsonrpc_monitor_selection { + OJMS_INITIAL = 1 << 0, /* All rows when monitor is created. */ + OJMS_INSERT = 1 << 1, /* New rows. */ + OJMS_DELETE = 1 << 2, /* Deleted rows. */ + OJMS_MODIFY = 1 << 3 /* Modified rows. */ +}; + +struct ovsdb_jsonrpc_monitor_table { + const struct ovsdb_table *table; + enum ovsdb_jsonrpc_monitor_selection select; + struct ovsdb_column_set columns; +}; + +struct ovsdb_jsonrpc_monitor { + struct ovsdb_replica replica; + struct ovsdb_jsonrpc_session *session; + struct hmap_node node; /* In ovsdb_jsonrpc_session's "monitors". */ + + struct json *monitor_id; + struct shash tables; /* Holds "struct ovsdb_jsonrpc_monitor_table"s. */ +}; + +static const struct ovsdb_replica_class ovsdb_jsonrpc_replica_class; + +struct ovsdb_jsonrpc_monitor *ovsdb_jsonrpc_monitor_find( + struct ovsdb_jsonrpc_session *, const struct json *monitor_id); +static void ovsdb_jsonrpc_monitor_destroy(struct ovsdb_replica *); +static struct json *ovsdb_jsonrpc_monitor_get_initial( + const struct ovsdb_jsonrpc_monitor *); + +static bool +parse_bool(struct ovsdb_parser *parser, const char *name, bool default_value) +{ + const struct json *json; + + json = ovsdb_parser_member(parser, name, OP_BOOLEAN | OP_OPTIONAL); + return json ? json_boolean(json) : default_value; +} + +struct ovsdb_jsonrpc_monitor * +ovsdb_jsonrpc_monitor_find(struct ovsdb_jsonrpc_session *s, + const struct json *monitor_id) +{ + struct ovsdb_jsonrpc_monitor *m; + + HMAP_FOR_EACH_WITH_HASH (m, struct ovsdb_jsonrpc_monitor, node, + json_hash(monitor_id, 0), &s->monitors) { + if (json_equal(m->monitor_id, monitor_id)) { + return m; + } + } + + return NULL; +} + +static struct json * +ovsdb_jsonrpc_monitor_create(struct ovsdb_jsonrpc_session *s, + struct json *params) +{ + struct ovsdb_jsonrpc_monitor *m = NULL; + struct json *monitor_id, *monitor_requests; + struct ovsdb_error *error = NULL; + struct shash_node *node; + struct json *json; + + if (json_array(params)->n != 3) { + error = ovsdb_syntax_error(params, NULL, "invalid parameters"); + goto error; + } + monitor_id = params->u.array.elems[1]; + monitor_requests = params->u.array.elems[2]; + if (monitor_requests->type != JSON_OBJECT) { + error = ovsdb_syntax_error(monitor_requests, NULL, + "monitor-requests must be object"); + goto error; + } + + if (ovsdb_jsonrpc_monitor_find(s, monitor_id)) { + error = ovsdb_syntax_error(monitor_id, NULL, "duplicate monitor ID"); + goto error; + } + + m = xzalloc(sizeof *m); + ovsdb_replica_init(&m->replica, &ovsdb_jsonrpc_replica_class); + ovsdb_add_replica(s->remote->server->db, &m->replica); + m->session = s; + hmap_insert(&s->monitors, &m->node, json_hash(monitor_id, 0)); + m->monitor_id = json_clone(monitor_id); + shash_init(&m->tables); + + SHASH_FOR_EACH (node, json_object(monitor_requests)) { + const struct ovsdb_table *table; + struct ovsdb_jsonrpc_monitor_table *mt; + const struct json *columns_json, *select_json; + struct ovsdb_parser parser; + + table = ovsdb_get_table(s->remote->server->db, node->name); + if (!table) { + error = ovsdb_syntax_error(NULL, NULL, + "no table named %s", node->name); + goto error; + } + + mt = xzalloc(sizeof *mt); + mt->table = table; + mt->select = OJMS_INITIAL | OJMS_INSERT | OJMS_DELETE | OJMS_MODIFY; + ovsdb_column_set_init(&mt->columns); + shash_add(&m->tables, table->schema->name, mt); + + ovsdb_parser_init(&parser, node->data, "table %s", node->name); + columns_json = ovsdb_parser_member(&parser, "columns", + OP_ARRAY | OP_OPTIONAL); + select_json = ovsdb_parser_member(&parser, "select", + OP_OBJECT | OP_OPTIONAL); + error = ovsdb_parser_finish(&parser); + if (error) { + goto error; + } + + if (columns_json) { + error = ovsdb_column_set_from_json(columns_json, table, + &mt->columns); + if (error) { + goto error; + } + } else { + struct shash_node *node; + + SHASH_FOR_EACH (node, &table->schema->columns) { + const struct ovsdb_column *column = node->data; + if (column->index != OVSDB_COL_UUID) { + ovsdb_column_set_add(&mt->columns, column); + } + } + } + + if (select_json) { + mt->select = 0; + ovsdb_parser_init(&parser, select_json, "table %s select", + table->schema->name); + if (parse_bool(&parser, "initial", true)) { + mt->select |= OJMS_INITIAL; + } + if (parse_bool(&parser, "insert", true)) { + mt->select |= OJMS_INSERT; + } + if (parse_bool(&parser, "delete", true)) { + mt->select |= OJMS_DELETE; + } + if (parse_bool(&parser, "modify", true)) { + mt->select |= OJMS_MODIFY; + } + error = ovsdb_parser_finish(&parser); + if (error) { + goto error; + } + } + } + + return ovsdb_jsonrpc_monitor_get_initial(m); + +error: + if (m) { + ovsdb_remove_replica(s->remote->server->db, &m->replica); + } + + json = ovsdb_error_to_json(error); + ovsdb_error_destroy(error); + return json; +} + +static struct jsonrpc_msg * +ovsdb_jsonrpc_monitor_cancel(struct ovsdb_jsonrpc_session *s, + struct json_array *params, + const struct json *request_id) +{ + if (params->n != 1) { + return jsonrpc_create_error(json_string_create("invalid parameters"), + request_id); + } else { + struct ovsdb_jsonrpc_monitor *m; + + m = ovsdb_jsonrpc_monitor_find(s, params->elems[0]); + if (!m) { + return jsonrpc_create_error(json_string_create("unknown monitor"), + request_id); + } else { + ovsdb_remove_replica(s->remote->server->db, &m->replica); + return jsonrpc_create_reply(json_object_create(), request_id); + } + } +} + +static void +ovsdb_jsonrpc_monitor_remove_all(struct ovsdb_jsonrpc_session *s) +{ + struct ovsdb_jsonrpc_monitor *m, *next; + + HMAP_FOR_EACH_SAFE (m, next, + struct ovsdb_jsonrpc_monitor, node, &s->monitors) { + ovsdb_remove_replica(s->remote->server->db, &m->replica); + } +} + +static struct ovsdb_jsonrpc_monitor * +ovsdb_jsonrpc_monitor_cast(struct ovsdb_replica *replica) +{ + assert(replica->class == &ovsdb_jsonrpc_replica_class); + return CONTAINER_OF(replica, struct ovsdb_jsonrpc_monitor, replica); +} + +struct ovsdb_jsonrpc_monitor_aux { + bool initial; /* Sending initial contents of table? */ + const struct ovsdb_jsonrpc_monitor *monitor; + struct json *json; /* JSON for the whole transaction. */ + + /* Current table. */ + struct ovsdb_jsonrpc_monitor_table *mt; + struct json *table_json; /* JSON for table's transaction. */ +}; + +static bool +ovsdb_jsonrpc_monitor_change_cb(const struct ovsdb_row *old, + const struct ovsdb_row *new, + void *aux_) +{ + struct ovsdb_jsonrpc_monitor_aux *aux = aux_; + const struct ovsdb_jsonrpc_monitor *m = aux->monitor; + struct ovsdb_table *table = new ? new->table : old->table; + enum ovsdb_jsonrpc_monitor_selection type; + struct json *old_json, *new_json; + struct json *row_json; + char uuid[UUID_LEN + 1]; + int n_changed; + size_t i; + + if (!aux->mt || table != aux->mt->table) { + aux->mt = shash_find_data(&m->tables, table->schema->name); + aux->table_json = NULL; + if (!aux->mt) { + /* We don't care about rows in this table at all. Tell the caller + * to skip it. */ + return false; + } + } + + type = (aux->initial ? OJMS_INITIAL + : !old ? OJMS_INSERT + : !new ? OJMS_DELETE + : OJMS_MODIFY); + if (!(aux->mt->select & type)) { + /* We don't care about this type of change (but do want to be called + * back for changes to other rows in the same table). */ + return true; + } + + old_json = new_json = NULL; + n_changed = 0; + for (i = 0; i < aux->mt->columns.n_columns; i++) { + const struct ovsdb_column *column = aux->mt->columns.columns[i]; + unsigned int idx = column->index; + bool changed = false; + + if (type == OJMS_MODIFY) { + changed = !ovsdb_datum_equals(&old->fields[idx], + &new->fields[idx], &column->type); + n_changed += changed; + } + if (changed || type == OJMS_DELETE) { + if (!old_json) { + old_json = json_object_create(); + } + json_object_put(old_json, column->name, + ovsdb_datum_to_json(&old->fields[idx], + &column->type)); + } + if (type & (OJMS_INITIAL | OJMS_INSERT | OJMS_MODIFY)) { + if (!new_json) { + new_json = json_object_create(); + } + json_object_put(new_json, column->name, + ovsdb_datum_to_json(&new->fields[idx], + &column->type)); + } + } + if ((type == OJMS_MODIFY && !n_changed) || (!old_json && !new_json)) { + /* No reportable changes. */ + json_destroy(old_json); + json_destroy(new_json); + return true; + } + + /* Create JSON object for transaction overall. */ + if (!aux->json) { + aux->json = json_object_create(); + } + + /* Create JSON object for transaction on this table. */ + if (!aux->table_json) { + aux->table_json = json_object_create(); + json_object_put(aux->json, aux->mt->table->schema->name, + aux->table_json); + } + + /* Create JSON object for transaction on this row. */ + row_json = json_object_create(); + if (old_json) { + json_object_put(row_json, "old", old_json); + } + if (new_json) { + json_object_put(row_json, "new", new_json); + } + + /* Add JSON row to JSON table. */ + snprintf(uuid, sizeof uuid, + UUID_FMT, UUID_ARGS(ovsdb_row_get_uuid(new ? new : old))); + json_object_put(aux->table_json, uuid, row_json); + + return true; +} + +static void +ovsdb_jsonrpc_monitor_init_aux(struct ovsdb_jsonrpc_monitor_aux *aux, + const struct ovsdb_jsonrpc_monitor *m, + bool initial) +{ + aux->initial = initial; + aux->monitor = m; + aux->json = NULL; + aux->mt = NULL; + aux->table_json = NULL; +} + +static struct ovsdb_error * +ovsdb_jsonrpc_monitor_commit(struct ovsdb_replica *replica, - const struct ovsdb_txn *txn, bool durable UNUSED) ++ const struct ovsdb_txn *txn, ++ bool durable OVS_UNUSED) +{ + struct ovsdb_jsonrpc_monitor *m = ovsdb_jsonrpc_monitor_cast(replica); + struct ovsdb_jsonrpc_monitor_aux aux; + + ovsdb_jsonrpc_monitor_init_aux(&aux, m, false); + ovsdb_txn_for_each_change(txn, ovsdb_jsonrpc_monitor_change_cb, &aux); + if (aux.json) { + struct jsonrpc_msg *msg; + struct json *params; + + params = json_array_create_2(json_clone(aux.monitor->monitor_id), + aux.json); + msg = jsonrpc_create_notify("update", params); + jsonrpc_session_send(aux.monitor->session->js, msg); + } + + return NULL; +} + +static struct json * +ovsdb_jsonrpc_monitor_get_initial(const struct ovsdb_jsonrpc_monitor *m) +{ + struct ovsdb_jsonrpc_monitor_aux aux; + struct shash_node *node; + + ovsdb_jsonrpc_monitor_init_aux(&aux, m, true); + SHASH_FOR_EACH (node, &m->tables) { + struct ovsdb_jsonrpc_monitor_table *mt = node->data; + + if (mt->select & OJMS_INITIAL) { + struct ovsdb_row *row; + + HMAP_FOR_EACH (row, struct ovsdb_row, hmap_node, + &mt->table->rows) { + ovsdb_jsonrpc_monitor_change_cb(NULL, row, &aux); + } + } + } + return aux.json ? aux.json : json_object_create(); +} + +static void +ovsdb_jsonrpc_monitor_destroy(struct ovsdb_replica *replica) +{ + struct ovsdb_jsonrpc_monitor *m = ovsdb_jsonrpc_monitor_cast(replica); + struct shash_node *node; + + json_destroy(m->monitor_id); + SHASH_FOR_EACH (node, &m->tables) { + struct ovsdb_jsonrpc_monitor_table *mt = node->data; + ovsdb_column_set_destroy(&mt->columns); + free(mt); + } + shash_destroy(&m->tables); + hmap_remove(&m->session->monitors, &m->node); + free(m); +} + +static const struct ovsdb_replica_class ovsdb_jsonrpc_replica_class = { + ovsdb_jsonrpc_monitor_commit, + ovsdb_jsonrpc_monitor_destroy +}; diff --cc ovsdb/ovsdb-client.c index df24e371,00000000..acc41854 mode 100644,000000..100644 --- a/ovsdb/ovsdb-client.c +++ b/ovsdb/ovsdb-client.c @@@ -1,926 -1,0 +1,926 @@@ +/* + * Copyright (c) 2009, 2010 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "command-line.h" +#include "column.h" +#include "compiler.h" +#include "daemon.h" +#include "dynamic-string.h" +#include "json.h" +#include "jsonrpc.h" +#include "ovsdb.h" +#include "ovsdb-error.h" +#include "stream.h" +#include "stream-ssl.h" +#include "table.h" +#include "timeval.h" +#include "util.h" + +#include "vlog.h" +#define THIS_MODULE VLM_ovsdb_client + +/* --format: Output formatting. */ +static enum { + FMT_TABLE, /* Textual table. */ + FMT_HTML, /* HTML table. */ + FMT_CSV /* Comma-separated lines. */ +} output_format; + +/* --wide: For --format=table, the maximum output width. */ +static int output_width; + +/* --no-headings: Whether table output should include headings. */ +static int output_headings = true; + +/* --pretty: Flags to pass to json_to_string(). */ +static int json_flags = JSSF_SORT; + +static const struct command all_commands[]; + +static void usage(void) NO_RETURN; +static void parse_options(int argc, char *argv[]); + +int +main(int argc, char *argv[]) +{ + proctitle_init(argc, argv); + set_program_name(argv[0]); + time_init(); + vlog_init(); + parse_options(argc, argv); + signal(SIGPIPE, SIG_IGN); + run_command(argc - optind, argv + optind, all_commands); + return 0; +} + +static void +parse_options(int argc, char *argv[]) +{ + enum { + OPT_BOOTSTRAP_CA_CERT = UCHAR_MAX + 1 + }; + static struct option long_options[] = { + {"wide", no_argument, &output_width, INT_MAX}, + {"format", required_argument, 0, 'f'}, + {"no-headings", no_argument, &output_headings, 0}, + {"pretty", no_argument, &json_flags, JSSF_PRETTY | JSSF_SORT}, + {"verbose", optional_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'V'}, + DAEMON_LONG_OPTIONS, +#ifdef HAVE_OPENSSL + {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT}, + STREAM_SSL_LONG_OPTIONS +#endif + {0, 0, 0, 0}, + }; + char *short_options = long_options_to_short_options(long_options); + + output_width = isatty(fileno(stdout)) ? 79 : INT_MAX; + for (;;) { + int c; + + c = getopt_long(argc, argv, short_options, long_options, NULL); + if (c == -1) { + break; + } + + switch (c) { + case 'f': + if (!strcmp(optarg, "table")) { + output_format = FMT_TABLE; + } else if (!strcmp(optarg, "html")) { + output_format = FMT_HTML; + } else if (!strcmp(optarg, "csv")) { + output_format = FMT_CSV; + } else { + ovs_fatal(0, "unknown output format \"%s\"", optarg); + } + break; + + case 'w': + output_width = INT_MAX; + break; + + case 'h': + usage(); + + case 'V': + OVS_PRINT_VERSION(0, 0); + exit(EXIT_SUCCESS); + + case 'v': + vlog_set_verbosity(optarg); + break; + + DAEMON_OPTION_HANDLERS + +#ifdef HAVE_OPENSSL + STREAM_SSL_OPTION_HANDLERS + + case OPT_BOOTSTRAP_CA_CERT: + stream_ssl_set_ca_cert_file(optarg, true); + break; +#endif + + case '?': + exit(EXIT_FAILURE); + + case 0: + /* getopt_long() already set the value for us. */ + break; + + default: + abort(); + } + } + free(short_options); +} + +static void +usage(void) +{ + printf("%s: Open vSwitch database JSON-RPC client\n" + "usage: %s [OPTIONS] COMMAND [ARG...]\n" + "\nValid commands are:\n" + "\n list-dbs SERVER\n" + " list databases available on SERVER\n" + "\n get-schema SERVER DATABASE\n" + " retrieve schema for DATABASE from SERVER\n" + "\n list-tables SERVER DATABSE\n" + " list tables for DATABSAE on SERVER\n" + "\n list-columns SERVER DATABASE [TABLE]\n" + " list columns in TABLE (or all tables) in DATABASE on SERVER\n" + "\n transact SERVER TRANSACTION\n" + " run TRANSACTION (a JSON array of operations) on SERVER\n" + " and print the results as JSON on stdout\n" + "\n monitor SERVER DATABASE TABLE [COLUMN,...] [SELECT,...]\n" + " monitor contents of (COLUMNs in) TABLE in DATABASE on SERVER\n" + " Valid SELECTs are: initial, insert, delete, modify\n", + program_name, program_name); + stream_usage("SERVER", true, true, true); + printf("\nOutput formatting options:\n" + " -f, --format=FORMAT set output formatting to FORMAT\n" + " (\"table\", \"html\", or \"csv\"\n" + " --wide don't limit TTY lines to 79 bytes\n" + " --no-headings omit table heading row\n" + " --pretty pretty-print JSON in output"); + daemon_usage(); + vlog_usage(); + printf("\nOther options:\n" + " -h, --help display this help message\n" + " -V, --version display version information\n"); + exit(EXIT_SUCCESS); +} + +static struct json * +parse_json(const char *s) +{ + struct json *json = json_from_string(s); + if (json->type == JSON_STRING) { + ovs_fatal(0, "\"%s\": %s", s, json->u.string); + } + return json; +} + +static struct jsonrpc * +open_jsonrpc(const char *server) +{ + struct stream *stream; + int error; + + error = stream_open_block(server, &stream); + if (error == EAFNOSUPPORT) { + struct pstream *pstream; + + error = pstream_open(server, &pstream); + if (error) { + ovs_fatal(error, "failed to connect or listen to \"%s\"", server); + } + + VLOG_INFO("%s: waiting for connection...", server); + error = pstream_accept_block(pstream, &stream); + if (error) { + ovs_fatal(error, "failed to accept connection on \"%s\"", server); + } + + pstream_close(pstream); + } else if (error) { + ovs_fatal(error, "failed to connect to \"%s\"", server); + } + + return jsonrpc_open(stream); +} + +static void +print_json(struct json *json) +{ + char *string = json_to_string(json, json_flags); + fputs(string, stdout); + free(string); +} + +static void +print_and_free_json(struct json *json) +{ + print_json(json); + json_destroy(json); +} + +static void +check_ovsdb_error(struct ovsdb_error *error) +{ + if (error) { + ovs_fatal(0, "%s", ovsdb_error_to_string(error)); + } +} + +static struct ovsdb_schema * +fetch_schema_from_rpc(struct jsonrpc *rpc, const char *database) +{ + struct jsonrpc_msg *request, *reply; + struct ovsdb_schema *schema; + int error; + + request = jsonrpc_create_request("get_schema", + json_array_create_1( + json_string_create(database)), + NULL); + error = jsonrpc_transact_block(rpc, request, &reply); + if (error) { + ovs_fatal(error, "transaction failed"); + } + check_ovsdb_error(ovsdb_schema_from_json(reply->result, &schema)); + jsonrpc_msg_destroy(reply); + + return schema; +} + +static struct ovsdb_schema * +fetch_schema(const char *server, const char *database) +{ + struct ovsdb_schema *schema; + struct jsonrpc *rpc; + + rpc = open_jsonrpc(server); + schema = fetch_schema_from_rpc(rpc, database); + jsonrpc_close(rpc); + + return schema; +} + +struct column { + char *heading; + int width; +}; + +struct table { + char **cells; + struct column *columns; + size_t n_columns, allocated_columns; + size_t n_rows, allocated_rows; + size_t current_column; +}; + +static void +table_init(struct table *table) +{ + memset(table, 0, sizeof *table); +} + +static void +table_destroy(struct table *table) +{ + size_t i; + + for (i = 0; i < table->n_columns; i++) { + free(table->columns[i].heading); + } + free(table->columns); + + for (i = 0; i < table->n_columns * table->n_rows; i++) { + free(table->cells[i]); + } + free(table->cells); +} + +static void +table_add_column(struct table *table, const char *heading, ...) + PRINTF_FORMAT(2, 3); + +static void +table_add_column(struct table *table, const char *heading, ...) +{ + struct column *column; + va_list args; + + assert(!table->n_rows); + if (table->n_columns >= table->allocated_columns) { + table->columns = x2nrealloc(table->columns, &table->allocated_columns, + sizeof *table->columns); + } + column = &table->columns[table->n_columns++]; + + va_start(args, heading); + column->heading = xvasprintf(heading, args); + column->width = strlen(column->heading); + va_end(args); +} + +static char ** +table_cell__(const struct table *table, size_t row, size_t column) +{ + return &table->cells[column + row * table->n_columns]; +} + +static void +table_add_row(struct table *table) +{ + size_t x, y; + + if (table->n_rows >= table->allocated_rows) { + table->cells = x2nrealloc(table->cells, &table->allocated_rows, + table->n_columns * sizeof *table->cells); + } + + y = table->n_rows++; + table->current_column = 0; + for (x = 0; x < table->n_columns; x++) { + *table_cell__(table, y, x) = NULL; + } +} + +static void +table_add_cell_nocopy(struct table *table, char *s) +{ + size_t x, y; + int length; + + assert(table->n_rows > 0); + assert(table->current_column < table->n_columns); + + x = table->current_column++; + y = table->n_rows - 1; + *table_cell__(table, y, x) = s; + + length = strlen(s); + if (length > table->columns[x].width) { + table->columns[x].width = length; + } +} + +static void +table_add_cell(struct table *table, const char *format, ...) +{ + va_list args; + + va_start(args, format); + table_add_cell_nocopy(table, xvasprintf(format, args)); + va_end(args); +} + +static void +table_print_table_line__(struct ds *line, size_t max_width) +{ + ds_truncate(line, max_width); + puts(ds_cstr(line)); + ds_clear(line); +} + +static void +table_print_table__(const struct table *table) +{ + struct ds line = DS_EMPTY_INITIALIZER; + size_t x, y; + + if (output_headings) { + for (x = 0; x < table->n_columns; x++) { + const struct column *column = &table->columns[x]; + if (x) { + ds_put_char(&line, ' '); + } + ds_put_format(&line, "%-*s", column->width, column->heading); + } + table_print_table_line__(&line, output_width); + + for (x = 0; x < table->n_columns; x++) { + const struct column *column = &table->columns[x]; + int i; + + if (x) { + ds_put_char(&line, ' '); + } + for (i = 0; i < column->width; i++) { + ds_put_char(&line, '-'); + } + } + table_print_table_line__(&line, output_width); + } + + for (y = 0; y < table->n_rows; y++) { + for (x = 0; x < table->n_columns; x++) { + const char *cell = *table_cell__(table, y, x); + if (x) { + ds_put_char(&line, ' '); + } + ds_put_format(&line, "%-*s", table->columns[x].width, cell); + } + table_print_table_line__(&line, output_width); + } + + ds_destroy(&line); +} + +static void +table_print_html_cell__(const char *element, const char *content) +{ + const char *p; + + printf(" <%s>", element); + for (p = content; *p != '\0'; p++) { + switch (*p) { + case '&': + fputs("&", stdout); + break; + case '<': + fputs("<", stdout); + break; + case '>': + fputs(">", stdout); + break; + default: + putchar(*p); + break; + } + } + printf("\n", element); +} + +static void +table_print_html__(const struct table *table) +{ + size_t x, y; + + fputs("\n", stdout); + + if (output_headings) { + fputs(" \n", stdout); + for (x = 0; x < table->n_columns; x++) { + const struct column *column = &table->columns[x]; + table_print_html_cell__("th", column->heading); + } + fputs(" \n", stdout); + } + + for (y = 0; y < table->n_rows; y++) { + fputs(" \n", stdout); + for (x = 0; x < table->n_columns; x++) { + table_print_html_cell__("td", *table_cell__(table, y, x)); + } + fputs(" \n", stdout); + } + + fputs("
\n", stdout); +} + +static void +table_print_csv_cell__(const char *content) +{ + const char *p; + + if (!strpbrk(content, "\n\",")) { + fputs(content, stdout); + } else { + putchar('"'); + for (p = content; *p != '\0'; p++) { + switch (*p) { + case '"': + fputs("\"\"", stdout); + break; + default: + putchar(*p); + break; + } + } + putchar('"'); + } +} + +static void +table_print_csv__(const struct table *table) +{ + size_t x, y; + + if (output_headings) { + for (x = 0; x < table->n_columns; x++) { + const struct column *column = &table->columns[x]; + if (x) { + putchar(','); + } + table_print_csv_cell__(column->heading); + } + putchar('\n'); + } + + for (y = 0; y < table->n_rows; y++) { + for (x = 0; x < table->n_columns; x++) { + if (x) { + putchar(','); + } + table_print_csv_cell__(*table_cell__(table, y, x)); + } + putchar('\n'); + } +} + +static void +table_print(const struct table *table) +{ + switch (output_format) { + case FMT_TABLE: + table_print_table__(table); + break; + + case FMT_HTML: + table_print_html__(table); + break; + + case FMT_CSV: + table_print_csv__(table); + break; + } +} + +static void - do_list_dbs(int argc UNUSED, char *argv[]) ++do_list_dbs(int argc OVS_UNUSED, char *argv[]) +{ + struct jsonrpc_msg *request, *reply; + struct jsonrpc *rpc; + int error; + size_t i; + + rpc = open_jsonrpc(argv[1]); + request = jsonrpc_create_request("list_dbs", json_array_create_empty(), + NULL); + error = jsonrpc_transact_block(rpc, request, &reply); + if (error) { + ovs_fatal(error, "transaction failed"); + } + + if (reply->result->type != JSON_ARRAY) { + ovs_fatal(0, "list_dbs response is not array"); + } + + for (i = 0; i < reply->result->u.array.n; i++) { + const struct json *name = reply->result->u.array.elems[i]; + + if (name->type != JSON_STRING) { + ovs_fatal(0, "list_dbs response %zu is not string", i); + } + puts(name->u.string); + } + jsonrpc_msg_destroy(reply); +} + +static void - do_get_schema(int argc UNUSED, char *argv[]) ++do_get_schema(int argc OVS_UNUSED, char *argv[]) +{ + struct ovsdb_schema *schema = fetch_schema(argv[1], argv[2]); + print_and_free_json(ovsdb_schema_to_json(schema)); + ovsdb_schema_destroy(schema); +} + +static void - do_list_tables(int argc UNUSED, char *argv[]) ++do_list_tables(int argc OVS_UNUSED, char *argv[]) +{ + struct ovsdb_schema *schema; + struct shash_node *node; + struct table t; + + schema = fetch_schema(argv[1], argv[2]); + table_init(&t); + table_add_column(&t, "Table"); + table_add_column(&t, "Comment"); + SHASH_FOR_EACH (node, &schema->tables) { + struct ovsdb_table_schema *ts = node->data; + + table_add_row(&t); + table_add_cell(&t, ts->name); + if (ts->comment) { + table_add_cell(&t, ts->comment); + } + } + ovsdb_schema_destroy(schema); + table_print(&t); +} + +static void - do_list_columns(int argc UNUSED, char *argv[]) ++do_list_columns(int argc OVS_UNUSED, char *argv[]) +{ + const char *table_name = argv[3]; + struct ovsdb_schema *schema; + struct shash_node *table_node; + struct table t; + + schema = fetch_schema(argv[1], argv[2]); + table_init(&t); + if (!table_name) { + table_add_column(&t, "Table"); + } + table_add_column(&t, "Column"); + table_add_column(&t, "Type"); + table_add_column(&t, "Comment"); + SHASH_FOR_EACH (table_node, &schema->tables) { + struct ovsdb_table_schema *ts = table_node->data; + + if (!table_name || !strcmp(table_name, ts->name)) { + struct shash_node *column_node; + + SHASH_FOR_EACH (column_node, &ts->columns) { + const struct ovsdb_column *column = column_node->data; + struct json *type = ovsdb_type_to_json(&column->type); + + table_add_row(&t); + if (!table_name) { + table_add_cell(&t, ts->name); + } + table_add_cell(&t, column->name); + table_add_cell_nocopy(&t, json_to_string(type, JSSF_SORT)); + if (column->comment) { + table_add_cell(&t, column->comment); + } + + json_destroy(type); + } + } + } + ovsdb_schema_destroy(schema); + table_print(&t); +} + +static void - do_transact(int argc UNUSED, char *argv[]) ++do_transact(int argc OVS_UNUSED, char *argv[]) +{ + struct jsonrpc_msg *request, *reply; + struct json *transaction; + struct jsonrpc *rpc; + int error; + + transaction = parse_json(argv[2]); + + rpc = open_jsonrpc(argv[1]); + request = jsonrpc_create_request("transact", transaction, NULL); + error = jsonrpc_transact_block(rpc, request, &reply); + if (error) { + ovs_fatal(error, "transaction failed"); + } + if (reply->error) { + ovs_fatal(error, "transaction returned error: %s", + json_to_string(reply->error, json_flags)); + } + print_json(reply->result); + putchar('\n'); + jsonrpc_msg_destroy(reply); + jsonrpc_close(rpc); +} + +static void +monitor_print_row(struct json *row, const char *type, const char *uuid, + const struct ovsdb_column_set *columns, struct table *t) +{ + size_t i; + + if (!row) { + ovs_error(0, "missing %s row", type); + return; + } else if (row->type != JSON_OBJECT) { + ovs_error(0, " is not object"); + return; + } + + table_add_row(t); + table_add_cell(t, uuid); + table_add_cell(t, type); + for (i = 0; i < columns->n_columns; i++) { + const struct ovsdb_column *column = columns->columns[i]; + struct json *value = shash_find_data(json_object(row), column->name); + if (value) { + table_add_cell_nocopy(t, json_to_string(value, JSSF_SORT)); + } else { + table_add_cell(t, ""); + } + } +} + +static void +monitor_print(struct json *table_updates, + const struct ovsdb_table_schema *table, + const struct ovsdb_column_set *columns, bool initial) +{ + struct json *table_update; + struct shash_node *node; + struct table t; + size_t i; + + table_init(&t); + + if (table_updates->type != JSON_OBJECT) { + ovs_error(0, " is not object"); + return; + } + table_update = shash_find_data(json_object(table_updates), table->name); + if (!table_update) { + return; + } + if (table_update->type != JSON_OBJECT) { + ovs_error(0, " is not object"); + return; + } + + table_add_column(&t, "row"); + table_add_column(&t, "action"); + for (i = 0; i < columns->n_columns; i++) { + table_add_column(&t, "%s", columns->columns[i]->name); + } + SHASH_FOR_EACH (node, json_object(table_update)) { + struct json *row_update = node->data; + struct json *old, *new; + + if (row_update->type != JSON_OBJECT) { + ovs_error(0, " is not object"); + continue; + } + old = shash_find_data(json_object(row_update), "old"); + new = shash_find_data(json_object(row_update), "new"); + if (initial) { + monitor_print_row(new, "initial", node->name, columns, &t); + } else if (!old) { + monitor_print_row(new, "insert", node->name, columns, &t); + } else if (!new) { + monitor_print_row(old, "delete", node->name, columns, &t); + } else { + monitor_print_row(old, "old", node->name, columns, &t); + monitor_print_row(new, "new", "", columns, &t); + } + } + table_print(&t); + table_destroy(&t); +} + +static void +do_monitor(int argc, char *argv[]) +{ + const char *server = argv[1]; + const char *database = argv[2]; + const char *table_name = argv[3]; + struct ovsdb_column_set columns = OVSDB_COLUMN_SET_INITIALIZER; + struct ovsdb_table_schema *table; + struct ovsdb_schema *schema; + struct jsonrpc_msg *request; + struct jsonrpc *rpc; + struct json *select, *monitor, *monitor_request, *monitor_requests, + *request_id; + + rpc = open_jsonrpc(server); + + schema = fetch_schema_from_rpc(rpc, database); + table = shash_find_data(&schema->tables, table_name); + if (!table) { + ovs_fatal(0, "%s: %s does not have a table named \"%s\"", + server, database, table_name); + } + + if (argc >= 5 && *argv[4] != '\0') { + char *save_ptr = NULL; + char *token; + + for (token = strtok_r(argv[4], ",", &save_ptr); token != NULL; + token = strtok_r(NULL, ",", &save_ptr)) { + const struct ovsdb_column *column; + column = ovsdb_table_schema_get_column(table, token); + if (!column) { + ovs_fatal(0, "%s: table \"%s\" in %s does not have a " + "column named \"%s\"", + server, table_name, database, token); + } + ovsdb_column_set_add(&columns, column); + } + } else { + struct shash_node *node; + + SHASH_FOR_EACH (node, &table->columns) { + const struct ovsdb_column *column = node->data; + if (column->index != OVSDB_COL_UUID) { + ovsdb_column_set_add(&columns, column); + } + } + } + + if (argc >= 6 && *argv[5] != '\0') { + char *save_ptr = NULL; + char *token; + + select = json_object_create(); + for (token = strtok_r(argv[5], ",", &save_ptr); token != NULL; + token = strtok_r(NULL, ",", &save_ptr)) { + json_object_put(select, token, json_boolean_create(true)); + } + } else { + select = NULL; + } + + monitor_request = json_object_create(); + json_object_put(monitor_request, + "columns", ovsdb_column_set_to_json(&columns)); + if (select) { + json_object_put(monitor_request, "select", select); + } + + monitor_requests = json_object_create(); + json_object_put(monitor_requests, table_name, monitor_request); + + monitor = json_array_create_3(json_string_create(database), + json_null_create(), monitor_requests); + request = jsonrpc_create_request("monitor", monitor, NULL); + request_id = json_clone(request->id); + jsonrpc_send(rpc, request); + for (;;) { + struct jsonrpc_msg *msg; + int error; + + error = jsonrpc_recv_block(rpc, &msg); + if (error) { + ovsdb_schema_destroy(schema); + ovs_fatal(error, "%s: receive failed", server); + } + + if (msg->type == JSONRPC_REQUEST && !strcmp(msg->method, "echo")) { + jsonrpc_send(rpc, jsonrpc_create_reply(json_clone(msg->params), + msg->id)); + } else if (msg->type == JSONRPC_REPLY + && json_equal(msg->id, request_id)) { + monitor_print(msg->result, table, &columns, true); + fflush(stdout); + if (get_detach()) { + /* daemonize() closes the standard file descriptors. We output + * to stdout, so we need to save and restore STDOUT_FILENO. */ + int fd = dup(STDOUT_FILENO); + daemonize(); + dup2(fd, STDOUT_FILENO); + close(fd); + } + } else if (msg->type == JSONRPC_NOTIFY + && !strcmp(msg->method, "update")) { + struct json *params = msg->params; + if (params->type == JSON_ARRAY + && params->u.array.n == 2 + && params->u.array.elems[0]->type == JSON_NULL) { + monitor_print(params->u.array.elems[1], + table, &columns, false); + fflush(stdout); + } + } + jsonrpc_msg_destroy(msg); + } +} + +static void - do_help(int argc UNUSED, char *argv[] UNUSED) ++do_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + usage(); +} + +static const struct command all_commands[] = { + { "list-dbs", 1, 1, do_list_dbs }, + { "get-schema", 2, 2, do_get_schema }, + { "list-tables", 2, 2, do_list_tables }, + { "list-columns", 2, 3, do_list_columns }, + { "transact", 2, 2, do_transact }, + { "monitor", 3, 5, do_monitor }, + { "help", 0, INT_MAX, do_help }, + { NULL, 0, 0, NULL }, +}; diff --cc ovsdb/ovsdb-idlc.in index a0f4a56d,00000000..9a90679e mode 100755,000000..100755 --- a/ovsdb/ovsdb-idlc.in +++ b/ovsdb/ovsdb-idlc.in @@@ -1,866 -1,0 +1,866 @@@ +#! @PYTHON@ + +import getopt +import os +import re +import sys + +sys.path.insert(0, "@abs_top_srcdir@/ovsdb") +import simplejson as json + +argv0 = sys.argv[0] + +class Error(Exception): + def __init__(self, msg): + Exception.__init__(self) + self.msg = msg + +def getMember(json, name, validTypes, description, default=None): + if name in json: + member = json[name] + if type(member) not in validTypes: + raise Error("%s: type mismatch for '%s' member" + % (description, name)) + return member + return default + +def mustGetMember(json, name, expectedType, description): + member = getMember(json, name, expectedType, description) + if member == None: + raise Error("%s: missing '%s' member" % (description, name)) + return member + +class DbSchema: + def __init__(self, name, comment, tables, idlPrefix, idlHeader): + self.name = name + self.comment = comment + self.tables = tables + self.idlPrefix = idlPrefix + self.idlHeader = idlHeader + + @staticmethod + def fromJson(json): + name = mustGetMember(json, 'name', [unicode], 'database') + comment = getMember(json, 'comment', [unicode], 'database') + tablesJson = mustGetMember(json, 'tables', [dict], 'database') + tables = {} + for tableName, tableJson in tablesJson.iteritems(): + tables[tableName] = TableSchema.fromJson(tableJson, + "%s table" % tableName) + idlPrefix = mustGetMember(json, 'idlPrefix', [unicode], 'database') + idlHeader = mustGetMember(json, 'idlHeader', [unicode], 'database') + return DbSchema(name, comment, tables, idlPrefix, idlHeader) + +class TableSchema: + def __init__(self, comment, columns): + self.comment = comment + self.columns = columns + + @staticmethod + def fromJson(json, description): + comment = getMember(json, 'comment', [unicode], description) + columnsJson = mustGetMember(json, 'columns', [dict], description) + columns = {} + for name, json in columnsJson.iteritems(): + columns[name] = ColumnSchema.fromJson( + json, "column %s in %s" % (name, description)) + return TableSchema(comment, columns) + +class ColumnSchema: + def __init__(self, comment, type, persistent): + self.comment = comment + self.type = type + self.persistent = persistent + + @staticmethod + def fromJson(json, description): + comment = getMember(json, 'comment', [unicode], description) + type = Type.fromJson(mustGetMember(json, 'type', [dict, unicode], + description), + 'type of %s' % description) + ephemeral = getMember(json, 'ephemeral', [bool], description) + persistent = ephemeral != True + return ColumnSchema(comment, type, persistent) + +def escapeCString(src): + dst = "" + for c in src: + if c in "\\\"": + dst += "\\" + c + elif ord(c) < 32: + if c == '\n': + dst += '\\n' + elif c == '\r': + dst += '\\r' + elif c == '\a': + dst += '\\a' + elif c == '\b': + dst += '\\b' + elif c == '\f': + dst += '\\f' + elif c == '\t': + dst += '\\t' + elif c == '\v': + dst += '\\v' + else: + dst += '\\%03o' % ord(c) + else: + dst += c + return dst + +class BaseType: + def __init__(self, type, refTable=None, minInteger=None, maxInteger=None, + minReal=None, maxReal=None, reMatch=None, reComment=None, + minLength=None, maxLength=None): + self.type = type + self.refTable = refTable + self.minInteger = minInteger + self.maxInteger = maxInteger + self.minReal = minReal + self.maxReal = maxReal + self.reMatch = reMatch + self.reComment = reComment + self.minLength = minLength + self.maxLength = maxLength + + @staticmethod + def fromJson(json, description): + if type(json) == unicode: + return BaseType(json) + else: + atomicType = mustGetMember(json, 'type', [unicode], description) + refTable = getMember(json, 'refTable', [unicode], description) + minInteger = getMember(json, 'minInteger', [int, long], description) + maxInteger = getMember(json, 'maxInteger', [int, long], description) + minReal = getMember(json, 'minReal', [int, long, float], description) + maxReal = getMember(json, 'maxReal', [int, long, float], description) + reMatch = getMember(json, 'reMatch', [unicode], description) + reComment = getMember(json, 'reComment', [unicode], description) + minLength = getMember(json, 'minLength', [int], description) + maxLength = getMember(json, 'minLength', [int], description) + return BaseType(atomicType, refTable, minInteger, maxInteger, minReal, maxReal, reMatch, reComment, minLength, maxLength) + + def toEnglish(self): + if self.type == 'uuid' and self.refTable: + return self.refTable + else: + return self.type + + def toCType(self, prefix): + if self.refTable: + return "struct %s%s *" % (prefix, self.refTable.lower()) + else: + return {'integer': 'int64_t ', + 'real': 'double ', + 'uuid': 'struct uuid ', + 'boolean': 'bool ', + 'string': 'char *'}[self.type] + + def copyCValue(self, dst, src): + args = {'dst': dst, 'src': src} + if self.refTable: + return ("%(dst)s = %(src)s->header_.uuid;") % args + elif self.type == 'string': + return "%(dst)s = xstrdup(%(src)s);" % args + else: + return "%(dst)s = %(src)s;" % args + + def initCDefault(self, var, isOptional): + if self.refTable: + return "%s = NULL;" % var + elif self.type == 'string' and not isOptional: + return "%s = \"\";" % var + else: + return {'integer': '%s = 0;', + 'real': '%s = 0.0;', + 'uuid': 'uuid_zero(&%s);', + 'boolean': '%s = false;', + 'string': '%s = NULL;'}[self.type] % var + + def cInitBaseType(self, indent, var): + stmts = [] + stmts.append('ovsdb_base_type_init(&%s, OVSDB_TYPE_%s);' % ( + var, self.type.upper()),) + if self.type == 'integer': + if self.minInteger != None: + stmts.append('%s.u.integer.min = %d;' % (var, self.minInteger)) + if self.maxInteger != None: + stmts.append('%s.u.integer.max = %d;' % (var, self.maxInteger)) + elif self.type == 'real': + if self.minReal != None: + stmts.append('%s.u.real.min = %d;' % (var, self.minReal)) + if self.maxReal != None: + stmts.append('%s.u.real.max = %d;' % (var, self.maxReal)) + elif self.type == 'string': + if self.reMatch != None: + if self.reComment != None: + reComment = '"%s"' % escapeCString(self.reComment) + else: + reComment = NULL + stmts.append('do_set_regex(&%s, "%s", %s);' % ( + var, escapeCString(self.reMatch), reComment)) + if self.minLength != None: + stmts.append('%s.u.string.minLen = %d;' % (var, self.minLength)) + if self.maxLength != None: + stmts.append('%s.u.string.maxLen = %d;' % (var, self.maxLength)) + elif self.type == 'uuid': + if self.refTable != None: + stmts.append('%s.u.uuid.refTableName = "%s";' % (var, escapeCString(self.refTable))) + return '\n'.join([indent + stmt for stmt in stmts]) + +class Type: + def __init__(self, key, value=None, min=1, max=1): + self.key = key + self.value = value + self.min = min + self.max = max + + @staticmethod + def fromJson(json, description): + if type(json) == unicode: + return Type(BaseType(json)) + else: + keyJson = mustGetMember(json, 'key', [dict, unicode], description) + key = BaseType.fromJson(keyJson, 'key in %s' % description) + + valueJson = getMember(json, 'value', [dict, unicode], description) + if valueJson: + value = BaseType.fromJson(valueJson, + 'value in %s' % description) + else: + value = None + + min = getMember(json, 'min', [int], description, 1) + max = getMember(json, 'max', [int, unicode], description, 1) + return Type(key, value, min, max) + + def isScalar(self): + return self.min == 1 and self.max == 1 and not self.value + + def isOptional(self): + return self.min == 0 and self.max == 1 + + def isOptionalPointer(self): + return (self.min == 0 and self.max == 1 and not self.value + and (self.key.type == 'string' or self.key.refTable)) + + def toEnglish(self): + keyName = self.key.toEnglish() + if self.value: + valueName = self.value.toEnglish() + + if self.isScalar(): + return keyName + elif self.isOptional(): + if self.value: + return "optional %s-%s pair" % (keyName, valueName) + else: + return "optional %s" % keyName + else: + if self.max == "unlimited": + if self.min: + quantity = "%d or more " % self.min + else: + quantity = "" + elif self.min: + quantity = "%d to %d " % (self.min, self.max) + else: + quantity = "up to %d " % self.max + + if self.value: + return "map of %s%s-%s pairs" % (quantity, keyName, valueName) + else: + return "set of %s%s" % (quantity, keyName) + + def cDeclComment(self): + if self.min == 1 and self.max == 1 and self.key.type == "string": + return "\t/* Always nonnull. */" + else: + return "" + + def cInitType(self, indent, var): + initKey = self.key.cInitBaseType(indent, "%s.key" % var) + if self.value: + initValue = self.value.cInitBaseType(indent, "%s.value" % var) + else: + initValue = ('%sovsdb_base_type_init(&%s.value, ' + 'OVSDB_TYPE_VOID);' % (indent, var)) + initMin = "%s%s.n_min = %s;" % (indent, var, self.min) + if self.max == "unlimited": + max = "UINT_MAX" + else: + max = self.max + initMax = "%s%s.n_max = %s;" % (indent, var, max) + return "\n".join((initKey, initValue, initMin, initMax)) + +def parseSchema(filename): + return DbSchema.fromJson(json.load(open(filename, "r"))) + +def annotateSchema(schemaFile, annotationFile): + schemaJson = json.load(open(schemaFile, "r")) + execfile(annotationFile, globals(), {"s": schemaJson}) + json.dump(schemaJson, sys.stdout) + +def constify(cType, const): + if (const + and cType.endswith('*') and not cType.endswith('**') + and (cType.startswith('struct uuid') or cType.startswith('char'))): + return 'const %s' % cType + else: + return cType + +def cMembers(prefix, columnName, column, const): + type = column.type + if type.min == 1 and type.max == 1: + singleton = True + pointer = '' + else: + singleton = False + if type.isOptionalPointer(): + pointer = '' + else: + pointer = '*' + + if type.value: + key = {'name': "key_%s" % columnName, + 'type': constify(type.key.toCType(prefix) + pointer, const), + 'comment': ''} + value = {'name': "value_%s" % columnName, + 'type': constify(type.value.toCType(prefix) + pointer, const), + 'comment': ''} + members = [key, value] + else: + m = {'name': columnName, + 'type': constify(type.key.toCType(prefix) + pointer, const), + 'comment': type.cDeclComment()} + members = [m] + + if not singleton and not type.isOptionalPointer(): + members.append({'name': 'n_%s' % columnName, + 'type': 'size_t ', + 'comment': ''}) + return members + +def printCIDLHeader(schemaFile): + schema = parseSchema(schemaFile) + prefix = schema.idlPrefix + print '''\ +/* Generated automatically -- do not modify! -*- buffer-read-only: t -*- */ + +#ifndef %(prefix)sIDL_HEADER +#define %(prefix)sIDL_HEADER 1 + +#include +#include +#include +#include "ovsdb-idl-provider.h" +#include "uuid.h"''' % {'prefix': prefix.upper()} + + for tableName, table in sorted(schema.tables.iteritems()): + structName = "%s%s" % (prefix, tableName.lower()) + + print " " + print "/* %s table. */" % tableName + print "struct %s {" % structName + print "\tstruct ovsdb_idl_row header_;" + for columnName, column in sorted(table.columns.iteritems()): + print "\n\t/* %s column. */" % columnName + for member in cMembers(prefix, columnName, column, False): + print "\t%(type)s%(name)s;%(comment)s" % member + print "};" + + # Column indexes. + printEnum(["%s_COL_%s" % (structName.upper(), columnName.upper()) + for columnName in sorted(table.columns)] + + ["%s_N_COLUMNS" % structName.upper()]) + + print + for columnName in table.columns: + print "#define %(s)s_col_%(c)s (%(s)s_columns[%(S)s_COL_%(C)s])" % { + 's': structName, + 'S': structName.upper(), + 'c': columnName, + 'C': columnName.upper()} + + print "\nextern struct ovsdb_idl_column %s_columns[%s_N_COLUMNS];" % (structName, structName.upper()) + + print ''' +const struct %(s)s *%(s)s_first(const struct ovsdb_idl *); +const struct %(s)s *%(s)s_next(const struct %(s)s *); +#define %(S)s_FOR_EACH(ROW, IDL) for ((ROW) = %(s)s_first(IDL); (ROW); (ROW) = %(s)s_next(ROW)) + +void %(s)s_delete(const struct %(s)s *); +struct %(s)s *%(s)s_insert(struct ovsdb_idl_txn *); +''' % {'s': structName, 'S': structName.upper()} + + for columnName, column in sorted(table.columns.iteritems()): + print 'void %(s)s_verify_%(c)s(const struct %(s)s *);' % {'s': structName, 'c': columnName} + + print + for columnName, column in sorted(table.columns.iteritems()): + + print 'void %(s)s_set_%(c)s(const struct %(s)s *,' % {'s': structName, 'c': columnName}, + args = ['%(type)s%(name)s' % member for member + in cMembers(prefix, columnName, column, True)] + print '%s);' % ', '.join(args) + + # Table indexes. + printEnum(["%sTABLE_%s" % (prefix.upper(), tableName.upper()) for tableName in sorted(schema.tables)] + ["%sN_TABLES" % prefix.upper()]) + print + for tableName in schema.tables: + print "#define %(p)stable_%(t)s (%(p)stable_classes[%(P)sTABLE_%(T)s])" % { + 'p': prefix, + 'P': prefix.upper(), + 't': tableName.lower(), + 'T': tableName.upper()} + print "\nextern struct ovsdb_idl_table_class %stable_classes[%sN_TABLES];" % (prefix, prefix.upper()) + + print "\nextern struct ovsdb_idl_class %sidl_class;" % prefix + print "\nvoid %sinit(void);" % prefix + print "\n#endif /* %(prefix)sIDL_HEADER */" % {'prefix': prefix.upper()} + +def printEnum(members): + if len(members) == 0: + return + + print "\nenum {"; + for member in members[:-1]: + print " %s," % member + print " %s" % members[-1] + print "};" + +def printCIDLSource(schemaFile): + schema = parseSchema(schemaFile) + prefix = schema.idlPrefix + print '''\ +/* Generated automatically -- do not modify! -*- buffer-read-only: t -*- */ + +#include +#include %s +#include +#include +#include "ovsdb-data.h" +#include "ovsdb-error.h" + +static bool inited; + - static void UNUSED ++static void OVS_UNUSED +do_set_regex(struct ovsdb_base_type *base, const char *reMatch, + const char *reComment) +{ + struct ovsdb_error *error; + + error = ovsdb_base_type_set_regex(base, reMatch, reComment); + if (error) { + char *s = ovsdb_error_to_string(error); + ovs_error(0, "%%s", s); + free(s); + ovsdb_error_destroy(error); + } +}''' % schema.idlHeader + + # Cast functions. + for tableName, table in sorted(schema.tables.iteritems()): + structName = "%s%s" % (prefix, tableName.lower()) + print ''' +static struct %(s)s * +%(s)s_cast(const struct ovsdb_idl_row *row) +{ + return row ? CONTAINER_OF(row, struct %(s)s, header_) : NULL; +}\ +''' % {'s': structName} + + + for tableName, table in sorted(schema.tables.iteritems()): + structName = "%s%s" % (prefix, tableName.lower()) + print " " + if table.comment != None: + print "/* %s table (%s). */" % (tableName, table.comment) + else: + print "/* %s table. */" % (tableName) + + # Parse functions. + for columnName, column in sorted(table.columns.iteritems()): + print ''' +static void +%(s)s_parse_%(c)s(struct ovsdb_idl_row *row_, const struct ovsdb_datum *datum) +{ + struct %(s)s *row = %(s)s_cast(row_);''' % {'s': structName, + 'c': columnName} + + type = column.type + if type.value: + keyVar = "row->key_%s" % columnName + valueVar = "row->value_%s" % columnName + else: + keyVar = "row->%s" % columnName + valueVar = None + + if (type.min == 1 and type.max == 1) or type.isOptionalPointer(): + print + print " assert(inited);" + print " if (datum->n >= 1) {" + if not type.key.refTable: + print " %s = datum->keys[0].%s;" % (keyVar, type.key.type) + else: + print " %s = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->keys[0].uuid));" % (keyVar, prefix, type.key.refTable.lower(), prefix, prefix.upper(), type.key.refTable.upper()) + + if valueVar: + if type.value.refTable: + print " %s = datum->values[0].%s;" % (valueVar, type.value.type) + else: + print " %s = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->values[0].uuid));" % (valueVar, prefix, type.value.refTable.lower(), prefix, prefix.upper(), type.value.refTable.upper()) + print " } else {" + print " %s" % type.key.initCDefault(keyVar, type.min == 0) + if valueVar: + print " %s" % type.value.initCDefault(valueVar, type.min == 0) + print " }" + else: + if type.max != 'unlimited': + print " size_t n = MIN(%d, datum->n);" % type.max + nMax = "n" + else: + nMax = "datum->n" + print " size_t i;" + print + print " assert(inited);" + print " %s = NULL;" % keyVar + if valueVar: + print " %s = NULL;" % valueVar + print " row->n_%s = 0;" % columnName + print " for (i = 0; i < %s; i++) {" % nMax + refs = [] + if type.key.refTable: + print " struct %s%s *keyRow = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->keys[i].uuid));" % (prefix, type.key.refTable.lower(), prefix, type.key.refTable.lower(), prefix, prefix.upper(), type.key.refTable.upper()) + keySrc = "keyRow" + refs.append('keyRow') + else: + keySrc = "datum->keys[i].%s" % type.key.type + if type.value and type.value.refTable: + print " struct %s%s *valueRow = %s%s_cast(ovsdb_idl_get_row_arc(row_, &%stable_classes[%sTABLE_%s], &datum->values[i].uuid));" % (prefix, type.value.refTable.lower(), prefix, type.value.refTable.lower(), prefix, prefix.upper(), type.value.refTable.upper()) + valueSrc = "valueRow" + refs.append('valueRow') + elif valueVar: + valueSrc = "datum->values[i].%s" % type.value.type + if refs: + print " if (%s) {" % ' && '.join(refs) + indent = " " + else: + indent = " " + print "%sif (!row->n_%s) {" % (indent, columnName) + print "%s %s = xmalloc(%s * sizeof *%s);" % (indent, keyVar, nMax, keyVar) + if valueVar: + print "%s %s = xmalloc(%s * sizeof %s);" % (indent, valueVar, nMax, valueVar) + print "%s}" % indent + print "%s%s[row->n_%s] = %s;" % (indent, keyVar, columnName, keySrc) + if valueVar: + print "%s%s[row->n_%s] = %s;" % (indent, valueVar, columnName, valueSrc) + print "%srow->n_%s++;" % (indent, columnName) + if refs: + print " }" + print " }" + print "}" + + # Unparse functions. + for columnName, column in sorted(table.columns.iteritems()): + type = column.type + if (type.min != 1 or type.max != 1) and not type.isOptionalPointer(): + print ''' +static void +%(s)s_unparse_%(c)s(struct ovsdb_idl_row *row_) +{ + struct %(s)s *row = %(s)s_cast(row_); + + assert(inited);''' % {'s': structName, 'c': columnName} + if type.value: + keyVar = "row->key_%s" % columnName + valueVar = "row->value_%s" % columnName + else: + keyVar = "row->%s" % columnName + valueVar = None + print " free(%s);" % keyVar + if valueVar: + print " free(%s);" % valueVar + print '}' + else: + print ''' +static void - %(s)s_unparse_%(c)s(struct ovsdb_idl_row *row UNUSED) ++%(s)s_unparse_%(c)s(struct ovsdb_idl_row *row OVS_UNUSED) +{ + /* Nothing to do. */ +}''' % {'s': structName, 'c': columnName} + + # First, next functions. + print ''' +const struct %(s)s * +%(s)s_first(const struct ovsdb_idl *idl) +{ + return %(s)s_cast(ovsdb_idl_first_row(idl, &%(p)stable_classes[%(P)sTABLE_%(T)s])); +} + +const struct %(s)s * +%(s)s_next(const struct %(s)s *row) +{ + return %(s)s_cast(ovsdb_idl_next_row(&row->header_)); +}''' % {'s': structName, + 'p': prefix, + 'P': prefix.upper(), + 'T': tableName.upper()} + + print ''' +void +%(s)s_delete(const struct %(s)s *row) +{ + ovsdb_idl_txn_delete(&row->header_); +} + +struct %(s)s * +%(s)s_insert(struct ovsdb_idl_txn *txn) +{ + return %(s)s_cast(ovsdb_idl_txn_insert(txn, &%(p)stable_classes[%(P)sTABLE_%(T)s])); +} +''' % {'s': structName, + 'p': prefix, + 'P': prefix.upper(), + 'T': tableName.upper()} + + # Verify functions. + for columnName, column in sorted(table.columns.iteritems()): + print ''' +void +%(s)s_verify_%(c)s(const struct %(s)s *row) +{ + assert(inited); + ovsdb_idl_txn_verify(&row->header_, &%(s)s_columns[%(S)s_COL_%(C)s]); +}''' % {'s': structName, + 'S': structName.upper(), + 'c': columnName, + 'C': columnName.upper()} + + # Set functions. + for columnName, column in sorted(table.columns.iteritems()): + type = column.type + print '\nvoid' + members = cMembers(prefix, columnName, column, True) + keyVar = members[0]['name'] + nVar = None + valueVar = None + if type.value: + valueVar = members[1]['name'] + if len(members) > 2: + nVar = members[2]['name'] + else: + if len(members) > 1: + nVar = members[1]['name'] + print '%(s)s_set_%(c)s(const struct %(s)s *row, %(args)s)' % \ + {'s': structName, 'c': columnName, + 'args': ', '.join(['%(type)s%(name)s' % m for m in members])} + print "{" + print " struct ovsdb_datum datum;" + if type.min == 1 and type.max == 1: + print + print " assert(inited);" + print " datum.n = 1;" + print " datum.keys = xmalloc(sizeof *datum.keys);" + print " " + type.key.copyCValue("datum.keys[0].%s" % type.key.type, keyVar) + if type.value: + print " datum.values = xmalloc(sizeof *datum.values);" + print " "+ type.value.copyCValue("datum.values[0].%s" % type.value.type, valueVar) + else: + print " datum.values = NULL;" + elif type.isOptionalPointer(): + print + print " assert(inited);" + print " if (%s) {" % keyVar + print " datum.n = 1;" + print " datum.keys = xmalloc(sizeof *datum.keys);" + print " " + type.key.copyCValue("datum.keys[0].%s" % type.key.type, keyVar) + print " } else {" + print " datum.n = 0;" + print " datum.keys = NULL;" + print " }" + print " datum.values = NULL;" + else: + print " size_t i;" + print + print " assert(inited);" + print " datum.n = %s;" % nVar + print " datum.keys = xmalloc(%s * sizeof *datum.keys);" % nVar + if type.value: + print " datum.values = xmalloc(%s * sizeof *datum.values);" % nVar + else: + print " datum.values = NULL;" + print " for (i = 0; i < %s; i++) {" % nVar + print " " + type.key.copyCValue("datum.keys[i].%s" % type.key.type, "%s[i]" % keyVar) + if type.value: + print " " + type.value.copyCValue("datum.values[i].%s" % type.value.type, "%s[i]" % valueVar) + print " }" + print " ovsdb_idl_txn_write(&row->header_, &%(s)s_columns[%(S)s_COL_%(C)s], &datum);" \ + % {'s': structName, + 'S': structName.upper(), + 'C': columnName.upper()} + print "}" + + # Table columns. + print "\nstruct ovsdb_idl_column %s_columns[%s_N_COLUMNS];" % ( + structName, structName.upper()) + print """ +static void\n%s_columns_init(void) +{ + struct ovsdb_idl_column *c;\ +""" % structName + for columnName, column in sorted(table.columns.iteritems()): + cs = "%s_col_%s" % (structName, columnName) + d = {'cs': cs, 'c': columnName, 's': structName} + print + print " /* Initialize %(cs)s. */" % d + print " c = &%(cs)s;" % d + print " c->name = \"%(c)s\";" % d + print column.type.cInitType(" ", "c->type") + print " c->parse = %(s)s_parse_%(c)s;" % d + print " c->unparse = %(s)s_unparse_%(c)s;" % d + print "}" + + # Table classes. + print " " + print "struct ovsdb_idl_table_class %stable_classes[%sN_TABLES] = {" % (prefix, prefix.upper()) + for tableName, table in sorted(schema.tables.iteritems()): + structName = "%s%s" % (prefix, tableName.lower()) + print " {\"%s\"," % tableName + print " %s_columns, ARRAY_SIZE(%s_columns)," % ( + structName, structName) + print " sizeof(struct %s)}," % structName + print "};" + + # IDL class. + print "\nstruct ovsdb_idl_class %sidl_class = {" % prefix + print " \"%s\", %stable_classes, ARRAY_SIZE(%stable_classes)" % ( + schema.name, prefix, prefix) + print "};" + + # global init function + print """ +void +%sinit(void) +{ + if (inited) { + return; + } + inited = true; +""" % prefix + for tableName, table in sorted(schema.tables.iteritems()): + structName = "%s%s" % (prefix, tableName.lower()) + print " %s_columns_init();" % structName + print "}" + +def ovsdb_escape(string): + def escape(match): + c = match.group(0) + if c == '\0': + raise Error("strings may not contain null bytes") + elif c == '\\': + return '\\\\' + elif c == '\n': + return '\\n' + elif c == '\r': + return '\\r' + elif c == '\t': + return '\\t' + elif c == '\b': + return '\\b' + elif c == '\a': + return '\\a' + else: + return '\\x%02x' % ord(c) + return re.sub(r'["\\\000-\037]', escape, string) + +def printDoc(schemaFile): + schema = parseSchema(schemaFile) + print schema.name + if schema.comment: + print schema.comment + + for tableName, table in sorted(schema.tables.iteritems()): + title = "%s table" % tableName + print + print title + print '-' * len(title) + if table.comment: + print table.comment + + for columnName, column in sorted(table.columns.iteritems()): + print + print "%s (%s)" % (columnName, column.type.toEnglish()) + if column.comment: + print "\t%s" % column.comment + +def usage(): + print """\ +%(argv0)s: ovsdb schema compiler +usage: %(argv0)s [OPTIONS] COMMAND ARG... + +The following commands are supported: + annotate SCHEMA ANNOTATIONS print SCHEMA combined with ANNOTATIONS + c-idl-header IDL print C header file for IDL + c-idl-source IDL print C source file for IDL implementation + doc IDL print schema documentation + +The following options are also available: + -h, --help display this help message + -V, --version display version information\ +""" % {'argv0': argv0} + sys.exit(0) + +if __name__ == "__main__": + try: + try: + options, args = getopt.gnu_getopt(sys.argv[1:], 'C:hV', + ['directory', + 'help', + 'version']) + except getopt.GetoptError, geo: + sys.stderr.write("%s: %s\n" % (argv0, geo.msg)) + sys.exit(1) + + for key, value in options: + if key in ['-h', '--help']: + usage() + elif key in ['-V', '--version']: + print "ovsdb-idlc (Open vSwitch) @VERSION@" + elif key in ['-C', '--directory']: + os.chdir(value) + else: + sys.exit(0) + + optKeys = [key for key, value in options] + + if not args: + sys.stderr.write("%s: missing command argument " + "(use --help for help)\n" % argv0) + sys.exit(1) + + commands = {"annotate": (annotateSchema, 2), + "c-idl-header": (printCIDLHeader, 1), + "c-idl-source": (printCIDLSource, 1), + "doc": (printDoc, 1)} + + if not args[0] in commands: + sys.stderr.write("%s: unknown command \"%s\" " + "(use --help for help)\n" % (argv0, args[0])) + sys.exit(1) + + func, n_args = commands[args[0]] + if len(args) - 1 != n_args: + sys.stderr.write("%s: \"%s\" requires %d arguments but %d " + "provided\n" + % (argv0, args[0], n_args, len(args) - 1)) + sys.exit(1) + + func(*args[1:]) + except Error, e: + sys.stderr.write("%s: %s\n" % (argv0, e.msg)) + sys.exit(1) + +# Local variables: +# mode: python +# End: diff --cc ovsdb/ovsdb-server.c index 03ebba17,00000000..eccbf7d1 mode 100644,000000..100644 --- a/ovsdb/ovsdb-server.c +++ b/ovsdb/ovsdb-server.c @@@ -1,308 -1,0 +1,308 @@@ +/* Copyright (c) 2009, 2010 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "ovsdb.h" + +#include +#include +#include +#include + +#include "column.h" +#include "command-line.h" +#include "daemon.h" +#include "file.h" +#include "json.h" +#include "jsonrpc.h" +#include "jsonrpc-server.h" +#include "leak-checker.h" +#include "list.h" +#include "ovsdb-data.h" +#include "ovsdb-types.h" +#include "ovsdb-error.h" +#include "poll-loop.h" +#include "process.h" +#include "row.h" +#include "stream-ssl.h" +#include "stream.h" +#include "svec.h" +#include "table.h" +#include "timeval.h" +#include "trigger.h" +#include "util.h" +#include "unixctl.h" + +#include "vlog.h" +#define THIS_MODULE VLM_ovsdb_server + +static unixctl_cb_func ovsdb_server_exit; + +static void parse_options(int argc, char *argv[], char **file_namep, + struct shash *remotes, char **unixctl_pathp); +static void usage(void) NO_RETURN; + +static void set_remotes(struct ovsdb_jsonrpc_server *jsonrpc, + const struct ovsdb *db, struct shash *remotes); + +int +main(int argc, char *argv[]) +{ + char *unixctl_path = NULL; + struct unixctl_server *unixctl; + struct ovsdb_jsonrpc_server *jsonrpc; + struct shash remotes; + struct ovsdb_error *error; + struct ovsdb *db; + char *file_name; + bool exiting; + int retval; + + proctitle_init(argc, argv); + set_program_name(argv[0]); + time_init(); + vlog_init(); + signal(SIGPIPE, SIG_IGN); + process_init(); + + parse_options(argc, argv, &file_name, &remotes, &unixctl_path); + + die_if_already_running(); + daemonize_start(); + + error = ovsdb_file_open(file_name, false, &db); + if (error) { + ovs_fatal(0, "%s", ovsdb_error_to_string(error)); + } + + jsonrpc = ovsdb_jsonrpc_server_create(db); + set_remotes(jsonrpc, db, &remotes); + + retval = unixctl_server_create(unixctl_path, &unixctl); + if (retval) { + exit(EXIT_FAILURE); + } + + daemonize_complete(); + + unixctl_command_register("exit", ovsdb_server_exit, &exiting); + + exiting = false; + while (!exiting) { + set_remotes(jsonrpc, db, &remotes); + ovsdb_jsonrpc_server_run(jsonrpc); + unixctl_server_run(unixctl); + ovsdb_trigger_run(db, time_msec()); + + ovsdb_jsonrpc_server_wait(jsonrpc); + unixctl_server_wait(unixctl); + ovsdb_trigger_wait(db, time_msec()); + poll_block(); + } + ovsdb_jsonrpc_server_destroy(jsonrpc); + ovsdb_destroy(db); + shash_destroy(&remotes); + unixctl_server_destroy(unixctl); + + return 0; +} + +static void +query_db_remotes(const char *name_, const struct ovsdb *db, + struct shash *remotes) +{ + char *name, *table_name, *column_name; + const struct ovsdb_column *column; + const struct ovsdb_table *table; + const struct ovsdb_row *row; + char *save_ptr = NULL; + + name = xstrdup(name_); + strtok_r(name, ":", &save_ptr); /* "db:" */ + table_name = strtok_r(NULL, ",", &save_ptr); + column_name = strtok_r(NULL, ",", &save_ptr); + if (!table_name || !column_name) { + ovs_fatal(0, "remote \"%s\": invalid syntax", name_); + } + + table = ovsdb_get_table(db, table_name); + if (!table) { + ovs_fatal(0, "remote \"%s\": no table named %s", name_, table_name); + } + + column = ovsdb_table_schema_get_column(table->schema, column_name); + if (!column) { + ovs_fatal(0, "remote \"%s\": table \"%s\" has no column \"%s\"", + name_, table_name, column_name); + } + + if (column->type.key.type != OVSDB_TYPE_STRING + || column->type.value.type != OVSDB_TYPE_VOID) { + ovs_fatal(0, "remote \"%s\": type of table \"%s\" column \"%s\" is " + "not string or set of strings", + name_, table_name, column_name); + } + + HMAP_FOR_EACH (row, struct ovsdb_row, hmap_node, &table->rows) { + const struct ovsdb_datum *datum; + size_t i; + + datum = &row->fields[column->index]; + for (i = 0; i < datum->n; i++) { + shash_add_once(remotes, datum->keys[i].string, NULL); + } + } + + free(name); +} + +static void +set_remotes(struct ovsdb_jsonrpc_server *jsonrpc, + const struct ovsdb *db, struct shash *remotes) +{ + struct shash resolved_remotes; + struct shash_node *node; + + shash_init(&resolved_remotes); + SHASH_FOR_EACH (node, remotes) { + const char *name = node->name; + + if (!strncmp(name, "db:", 3)) { + query_db_remotes(name, db, &resolved_remotes); + } else { + shash_add_once(&resolved_remotes, name, NULL); + } + } + ovsdb_jsonrpc_server_set_remotes(jsonrpc, &resolved_remotes); + shash_destroy(&resolved_remotes); +} + + +static void - ovsdb_server_exit(struct unixctl_conn *conn, const char *args UNUSED, ++ovsdb_server_exit(struct unixctl_conn *conn, const char *args OVS_UNUSED, + void *exiting_) +{ + bool *exiting = exiting_; + *exiting = true; + unixctl_command_reply(conn, 200, NULL); +} + +static void +parse_options(int argc, char *argv[], char **file_namep, + struct shash *remotes, char **unixctl_pathp) +{ + enum { + OPT_DUMMY = UCHAR_MAX + 1, + OPT_REMOTE, + OPT_UNIXCTL, + OPT_BOOTSTRAP_CA_CERT, + VLOG_OPTION_ENUMS, + LEAK_CHECKER_OPTION_ENUMS + }; + static struct option long_options[] = { + {"remote", required_argument, 0, OPT_REMOTE}, + {"unixctl", required_argument, 0, OPT_UNIXCTL}, + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'V'}, + DAEMON_LONG_OPTIONS, + VLOG_LONG_OPTIONS, + LEAK_CHECKER_LONG_OPTIONS, +#ifdef HAVE_OPENSSL + {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT}, + STREAM_SSL_LONG_OPTIONS +#endif + {0, 0, 0, 0}, + }; + char *short_options = long_options_to_short_options(long_options); + + shash_init(remotes); + for (;;) { + int c; + + c = getopt_long(argc, argv, short_options, long_options, NULL); + if (c == -1) { + break; + } + + switch (c) { + case OPT_REMOTE: + shash_add_once(remotes, optarg, NULL); + break; + + case OPT_UNIXCTL: + *unixctl_pathp = optarg; + break; + + case 'h': + usage(); + + case 'V': + OVS_PRINT_VERSION(0, 0); + exit(EXIT_SUCCESS); + + VLOG_OPTION_HANDLERS + DAEMON_OPTION_HANDLERS + LEAK_CHECKER_OPTION_HANDLERS + +#ifdef HAVE_OPENSSL + STREAM_SSL_OPTION_HANDLERS + + case OPT_BOOTSTRAP_CA_CERT: + stream_ssl_set_ca_cert_file(optarg, true); + break; +#endif + + + case '?': + exit(EXIT_FAILURE); + + default: + abort(); + } + } + free(short_options); + + argc -= optind; + argv += optind; + + if (argc > 1) { + ovs_fatal(0, "database file is only non-option argument; " + "use --help for usage"); + } else if (argc < 1) { + ovs_fatal(0, "missing database file argument; use --help for usage"); + } + + *file_namep = argv[0]; +} + +static void +usage(void) +{ + printf("%s: Open vSwitch database server\n" + "usage: %s [OPTIONS] DATABASE\n" + "where DATABASE is a database file in ovsdb format.\n", + program_name, program_name); + printf("\nJSON-RPC options (may be specified any number of times):\n" + " --remote=REMOTE connect or listen to REMOTE\n"); + stream_usage("JSON-RPC", true, true, true); + daemon_usage(); + vlog_usage(); + printf("\nOther options:\n" + " -h, --help display this help message\n" + " -V, --version display version information\n"); + leak_checker_usage(); + exit(EXIT_SUCCESS); +} diff --cc ovsdb/ovsdb-tool.c index 1f2a5ef7,00000000..34c76761 mode 100644,000000..100644 --- a/ovsdb/ovsdb-tool.c +++ b/ovsdb/ovsdb-tool.c @@@ -1,344 -1,0 +1,344 @@@ +/* + * Copyright (c) 2009, 2010 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "command-line.h" +#include "compiler.h" +#include "file.h" +#include "log.h" +#include "json.h" +#include "ovsdb.h" +#include "ovsdb-error.h" +#include "table.h" +#include "timeval.h" +#include "util.h" + +#include "vlog.h" +#define THIS_MODULE VLM_ovsdb_tool + +/* -m, --more: Verbosity level for "show-log" command output. */ +static int show_log_verbosity; + +static const struct command all_commands[]; + +static void usage(void) NO_RETURN; +static void parse_options(int argc, char *argv[]); + +int +main(int argc, char *argv[]) +{ + set_program_name(argv[0]); + time_init(); + vlog_init(); + parse_options(argc, argv); + signal(SIGPIPE, SIG_IGN); + run_command(argc - optind, argv + optind, all_commands); + return 0; +} + +static void +parse_options(int argc, char *argv[]) +{ + static struct option long_options[] = { + {"more", no_argument, 0, 'm'}, + {"verbose", optional_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'V'}, + {0, 0, 0, 0}, + }; + char *short_options = long_options_to_short_options(long_options); + + for (;;) { + int c; + + c = getopt_long(argc, argv, short_options, long_options, NULL); + if (c == -1) { + break; + } + + switch (c) { + case 'm': + show_log_verbosity++; + break; + + case 'h': + usage(); + + case 'V': + OVS_PRINT_VERSION(0, 0); + exit(EXIT_SUCCESS); + + case 'v': + vlog_set_verbosity(optarg); + break; + + case '?': + exit(EXIT_FAILURE); + + default: + abort(); + } + } + free(short_options); +} + +static void +usage(void) +{ + printf("%s: Open vSwitch database management utility\n" + "usage: %s [OPTIONS] COMMAND [ARG...]\n" + " create DB SCHEMA create DB with the given SCHEMA\n" + " compact DB [DST] compact DB in-place (or to DST)\n" + " extract-schema DB print DB's schema on stdout\n" + " query DB TRNS execute read-only transaction on DB\n" + " transact DB TRNS execute read/write transaction on DB\n" + " show-log DB prints information about DB's log entries\n", + program_name, program_name); + vlog_usage(); + printf("\nOther options:\n" + " -m, --more increase show-log verbosity\n" + " -h, --help display this help message\n" + " -V, --version display version information\n"); + exit(EXIT_SUCCESS); +} + +static struct json * +parse_json(const char *s) +{ + struct json *json = json_from_string(s); + if (json->type == JSON_STRING) { + ovs_fatal(0, "\"%s\": %s", s, json->u.string); + } + return json; +} + +static void +print_and_free_json(struct json *json) +{ + char *string = json_to_string(json, JSSF_SORT); + json_destroy(json); + puts(string); + free(string); +} + +static void +check_ovsdb_error(struct ovsdb_error *error) +{ + if (error) { + ovs_fatal(0, "%s", ovsdb_error_to_string(error)); + } +} + +static void - do_create(int argc UNUSED, char *argv[]) ++do_create(int argc OVS_UNUSED, char *argv[]) +{ + const char *db_file_name = argv[1]; + const char *schema_file_name = argv[2]; + struct ovsdb_schema *schema; + struct ovsdb_log *log; + struct json *json; + + /* Read schema from file and convert to JSON. */ + check_ovsdb_error(ovsdb_schema_from_file(schema_file_name, &schema)); + json = ovsdb_schema_to_json(schema); + ovsdb_schema_destroy(schema); + + /* Create database file. */ + check_ovsdb_error(ovsdb_log_open(db_file_name, O_RDWR | O_CREAT | O_EXCL, + &log)); + check_ovsdb_error(ovsdb_log_write(log, json)); + check_ovsdb_error(ovsdb_log_commit(log)); + ovsdb_log_close(log); + + json_destroy(json); +} + +static void +transact(bool read_only, const char *db_file_name, const char *transaction) +{ + struct json *request, *result; + struct ovsdb *db; + + check_ovsdb_error(ovsdb_file_open(db_file_name, read_only, &db)); + + request = parse_json(transaction); + result = ovsdb_execute(db, request, 0, NULL); + json_destroy(request); + + print_and_free_json(result); + ovsdb_destroy(db); +} + +static void - do_query(int argc UNUSED, char *argv[]) ++do_query(int argc OVS_UNUSED, char *argv[]) +{ + transact(true, argv[1], argv[2]); +} + +static void - do_transact(int argc UNUSED, char *argv[]) ++do_transact(int argc OVS_UNUSED, char *argv[]) +{ + transact(false, argv[1], argv[2]); +} + +static void +print_db_changes(struct shash *tables, struct shash *names) +{ + struct shash_node *n1; + + SHASH_FOR_EACH (n1, tables) { + const char *table = n1->name; + struct json *rows = n1->data; + struct shash_node *n2; + + if (n1->name[0] == '_' || rows->type != JSON_OBJECT) { + continue; + } + + SHASH_FOR_EACH (n2, json_object(rows)) { + const char *row_uuid = n2->name; + struct json *columns = n2->data; + struct shash_node *n3; + char *old_name, *new_name; + bool free_new_name = false; + + old_name = new_name = shash_find_data(names, row_uuid); + if (columns->type == JSON_OBJECT) { + struct json *new_name_json; + + new_name_json = shash_find_data(json_object(columns), "name"); + if (new_name_json) { + new_name = json_to_string(new_name_json, JSSF_SORT); + free_new_name = true; + } + } + + printf("\ttable %s", table); + + if (!old_name) { + if (new_name) { + printf(" insert row %s:\n", new_name); + } else { + printf(" insert row %.8s:\n", row_uuid); + } + } else { + printf(" row %s:\n", old_name); + } + + if (columns->type == JSON_OBJECT) { + if (show_log_verbosity > 1) { + SHASH_FOR_EACH (n3, json_object(columns)) { + const char *column = n3->name; + struct json *value = n3->data; + char *value_string; + + value_string = json_to_string(value, JSSF_SORT); + printf("\t\t%s=%s\n", column, value_string); + free(value_string); + } + } + if (!old_name + || (new_name != old_name && strcmp(old_name, new_name))) { + if (old_name) { + shash_delete(names, shash_find(names, row_uuid)); + free(old_name); + } + shash_add(names, row_uuid, (new_name + ? xstrdup(new_name) + : xmemdup0(row_uuid, 8))); + } + } else if (columns->type == JSON_NULL) { + printf("\t\tdelete row\n"); + shash_delete(names, shash_find(names, row_uuid)); + free(old_name); + } + + if (free_new_name) { + free(new_name); + } + } + } +} + +static void - do_show_log(int argc UNUSED, char *argv[]) ++do_show_log(int argc OVS_UNUSED, char *argv[]) +{ + const char *db_file_name = argv[1]; + struct shash names; + struct ovsdb_log *log; + unsigned int i; + + check_ovsdb_error(ovsdb_log_open(db_file_name, O_RDONLY, &log)); + shash_init(&names); + for (i = 0; ; i++) { + struct json *json; + + check_ovsdb_error(ovsdb_log_read(log, &json)); + if (!json) { + break; + } + + printf("record %u:", i); + if (json->type == JSON_OBJECT) { + struct json *date, *comment; + + date = shash_find_data(json_object(json), "_date"); + if (date && date->type == JSON_INTEGER) { + time_t t = json_integer(date); + char s[128]; + + strftime(s, sizeof s, "%Y-%m-%d %H:%M:%S", localtime(&t)); + printf(" %s", s); + } + + comment = shash_find_data(json_object(json), "_comment"); + if (comment && comment->type == JSON_STRING) { + printf(" \"%s\"", json_string(comment)); + } + + if (i > 0 && show_log_verbosity > 0) { + putchar('\n'); + print_db_changes(json_object(json), &names); + } + } + json_destroy(json); + putchar('\n'); + } + + /* XXX free 'names'. */ +} + +static void - do_help(int argc UNUSED, char *argv[] UNUSED) ++do_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + usage(); +} + +static const struct command all_commands[] = { + { "create", 2, 2, do_create }, + { "query", 2, 2, do_query }, + { "transact", 2, 2, do_transact }, + { "show-log", 1, 1, do_show_log }, + { "help", 0, INT_MAX, do_help }, + { NULL, 0, 0, NULL }, +}; diff --cc ovsdb/ovsdb.c index 2dea507c,00000000..2b5bdc32 mode 100644,000000..100644 --- a/ovsdb/ovsdb.c +++ b/ovsdb/ovsdb.c @@@ -1,294 -1,0 +1,294 @@@ +/* Copyright (c) 2009, 2010 Nicira Networks + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "ovsdb.h" + +#include "column.h" +#include "json.h" +#include "ovsdb-error.h" +#include "ovsdb-parser.h" +#include "ovsdb-types.h" +#include "table.h" +#include "transaction.h" + +struct ovsdb_schema * +ovsdb_schema_create(const char *name, const char *comment) +{ + struct ovsdb_schema *schema; + + schema = xzalloc(sizeof *schema); + schema->name = xstrdup(name); + schema->comment = comment ? xstrdup(comment) : NULL; + shash_init(&schema->tables); + + return schema; +} + +void +ovsdb_schema_destroy(struct ovsdb_schema *schema) +{ + struct shash_node *node; + + SHASH_FOR_EACH (node, &schema->tables) { + ovsdb_table_schema_destroy(node->data); + } + shash_destroy(&schema->tables); + free(schema->comment); + free(schema->name); + free(schema); +} + +struct ovsdb_error * +ovsdb_schema_from_file(const char *file_name, struct ovsdb_schema **schemap) +{ + struct ovsdb_schema *schema; + struct ovsdb_error *error; + struct json *json; + + *schemap = NULL; + json = json_from_file(file_name); + if (json->type == JSON_STRING) { + error = ovsdb_error("failed to read schema", + "\"%s\" could not be read as JSON (%s)", + file_name, json_string(json)); + json_destroy(json); + return error; + } + + error = ovsdb_schema_from_json(json, &schema); + json_destroy(json); + if (error) { + return ovsdb_wrap_error(error, + "failed to parse \"%s\" as ovsdb schema", + file_name); + } + + *schemap = schema; + return NULL; +} + +static struct ovsdb_error * WARN_UNUSED_RESULT +ovsdb_schema_check_ref_table(const struct ovsdb_column *column, + const struct shash *tables, + const struct ovsdb_base_type *base, + const char *base_name) +{ + if (base->type == OVSDB_TYPE_UUID && base->u.uuid.refTableName + && !shash_find(tables, base->u.uuid.refTableName)) { + return ovsdb_syntax_error(NULL, NULL, + "column %s %s refers to undefined table %s", + column->name, base_name, + base->u.uuid.refTableName); + } else { + return NULL; + } +} + +struct ovsdb_error * +ovsdb_schema_from_json(struct json *json, struct ovsdb_schema **schemap) +{ + struct ovsdb_schema *schema; + const struct json *name, *comment, *tables; + struct ovsdb_error *error; + struct shash_node *node; + struct ovsdb_parser parser; + + *schemap = NULL; + + ovsdb_parser_init(&parser, json, "database schema"); + name = ovsdb_parser_member(&parser, "name", OP_ID); + comment = ovsdb_parser_member(&parser, "comment", OP_STRING | OP_OPTIONAL); + tables = ovsdb_parser_member(&parser, "tables", OP_OBJECT); + error = ovsdb_parser_finish(&parser); + if (error) { + return error; + } + + schema = ovsdb_schema_create(json_string(name), + comment ? json_string(comment) : NULL); + SHASH_FOR_EACH (node, json_object(tables)) { + struct ovsdb_table_schema *table; + + if (node->name[0] == '_') { + error = ovsdb_syntax_error(json, NULL, "names beginning with " + "\"_\" are reserved"); + } else if (!ovsdb_parser_is_id(node->name)) { + error = ovsdb_syntax_error(json, NULL, "name must be a valid id"); + } else { + error = ovsdb_table_schema_from_json(node->data, node->name, + &table); + } + if (error) { + ovsdb_schema_destroy(schema); + return error; + } + + shash_add(&schema->tables, table->name, table); + } + + /* Validate that all refTables refer to the names of tables that exist. */ + SHASH_FOR_EACH (node, &schema->tables) { + struct ovsdb_table_schema *table = node->data; + struct shash_node *node2; + + SHASH_FOR_EACH (node2, &table->columns) { + struct ovsdb_column *column = node2->data; + + error = ovsdb_schema_check_ref_table(column, &schema->tables, + &column->type.key, "key"); + if (!error) { + error = ovsdb_schema_check_ref_table(column, &schema->tables, + &column->type.value, + "value"); + } + if (error) { + ovsdb_schema_destroy(schema); + return error; + } + } + } + + *schemap = schema; + return 0; +} + +struct json * +ovsdb_schema_to_json(const struct ovsdb_schema *schema) +{ + struct json *json, *tables; + struct shash_node *node; + + json = json_object_create(); + json_object_put_string(json, "name", schema->name); + if (schema->comment) { + json_object_put_string(json, "comment", schema->comment); + } + + tables = json_object_create(); + + SHASH_FOR_EACH (node, &schema->tables) { + struct ovsdb_table_schema *table = node->data; + json_object_put(tables, table->name, + ovsdb_table_schema_to_json(table)); + } + json_object_put(json, "tables", tables); + + return json; +} + +static void +ovsdb_set_ref_table(const struct shash *tables, + struct ovsdb_base_type *base) +{ + if (base->type == OVSDB_TYPE_UUID && base->u.uuid.refTableName) { + struct ovsdb_table *table; + + table = shash_find_data(tables, base->u.uuid.refTableName); + base->u.uuid.refTable = table; + } +} + +struct ovsdb * +ovsdb_create(struct ovsdb_schema *schema) +{ + struct shash_node *node; + struct ovsdb *db; + + db = xmalloc(sizeof *db); + db->schema = schema; + list_init(&db->replicas); + list_init(&db->triggers); + db->run_triggers = false; + + shash_init(&db->tables); + SHASH_FOR_EACH (node, &schema->tables) { + struct ovsdb_table_schema *ts = node->data; + shash_add(&db->tables, node->name, ovsdb_table_create(ts)); + } + + /* Set all the refTables. */ + SHASH_FOR_EACH (node, &schema->tables) { + struct ovsdb_table_schema *table = node->data; + struct shash_node *node2; + + SHASH_FOR_EACH (node2, &table->columns) { + struct ovsdb_column *column = node2->data; + + ovsdb_set_ref_table(&db->tables, &column->type.key); + ovsdb_set_ref_table(&db->tables, &column->type.value); + } + } + + return db; +} + +void +ovsdb_destroy(struct ovsdb *db) +{ + if (db) { + struct shash_node *node; + + /* Remove all the replicas. */ + while (!list_is_empty(&db->replicas)) { + struct ovsdb_replica *r + = CONTAINER_OF(list_pop_back(&db->replicas), + struct ovsdb_replica, node); + ovsdb_remove_replica(db, r); + } + + /* Delete all the tables. This also deletes their schemas. */ + SHASH_FOR_EACH (node, &db->tables) { + struct ovsdb_table *table = node->data; + ovsdb_table_destroy(table); + } + shash_destroy(&db->tables); + + /* The schemas, but not the table that points to them, were deleted in + * the previous step, so we need to clear out the table. We can't + * destroy the table, because ovsdb_schema_destroy() will do that. */ + shash_clear(&db->schema->tables); + + ovsdb_schema_destroy(db->schema); + free(db); + } +} + +struct ovsdb_table * +ovsdb_get_table(const struct ovsdb *db, const char *name) +{ + return shash_find_data(&db->tables, name); +} + +void +ovsdb_replica_init(struct ovsdb_replica *r, + const struct ovsdb_replica_class *class) +{ + r->class = class; +} + +void +ovsdb_add_replica(struct ovsdb *db, struct ovsdb_replica *r) +{ + list_push_back(&db->replicas, &r->node); +} + +void - ovsdb_remove_replica(struct ovsdb *db UNUSED, struct ovsdb_replica *r) ++ovsdb_remove_replica(struct ovsdb *db OVS_UNUSED, struct ovsdb_replica *r) +{ + list_remove(&r->node); + (r->class->destroy)(r); +} diff --cc tests/test-jsonrpc.c index 03d30001,00000000..06b1cf47 mode 100644,000000..100644 --- a/tests/test-jsonrpc.c +++ b/tests/test-jsonrpc.c @@@ -1,341 -1,0 +1,341 @@@ +/* + * Copyright (c) 2009, 2010 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "jsonrpc.h" + +#include +#include +#include +#include +#include + +#include "command-line.h" +#include "daemon.h" +#include "json.h" +#include "poll-loop.h" +#include "stream-ssl.h" +#include "stream.h" +#include "timeval.h" +#include "util.h" +#include "vlog.h" + +static struct command all_commands[]; + +static void usage(void) NO_RETURN; +static void parse_options(int argc, char *argv[]); + +int +main(int argc, char *argv[]) +{ + proctitle_init(argc, argv); + set_program_name(argv[0]); + time_init(); + vlog_init(); + parse_options(argc, argv); + run_command(argc - optind, argv + optind, all_commands); + return 0; +} + +static void +parse_options(int argc, char *argv[]) +{ + enum { + OPT_BOOTSTRAP_CA_CERT = UCHAR_MAX + 1 + }; + static struct option long_options[] = { + {"verbose", optional_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + DAEMON_LONG_OPTIONS, +#ifdef HAVE_OPENSSL + {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT}, + STREAM_SSL_LONG_OPTIONS +#endif + {0, 0, 0, 0}, + }; + char *short_options = long_options_to_short_options(long_options); + + for (;;) { + int c = getopt_long(argc, argv, short_options, long_options, NULL); + if (c == -1) { + break; + } + + switch (c) { + case 'h': + usage(); + + case 'v': + vlog_set_verbosity(optarg); + break; + + DAEMON_OPTION_HANDLERS + +#ifdef HAVE_OPENSSL + STREAM_SSL_OPTION_HANDLERS + + case OPT_BOOTSTRAP_CA_CERT: + stream_ssl_set_ca_cert_file(optarg, true); + break; +#endif + + case '?': + exit(EXIT_FAILURE); + + default: + abort(); + } + } + free(short_options); +} + +static void +usage(void) +{ + printf("%s: JSON-RPC test utility\n" + "usage: %s [OPTIONS] COMMAND [ARG...]\n" + " listen LOCAL listen for connections on LOCAL\n" + " request REMOTE METHOD PARAMS send request, print reply\n" + " notify REMOTE METHOD PARAMS send notification and exit\n", + program_name, program_name); + stream_usage("JSON-RPC", true, true, true); + daemon_usage(); + vlog_usage(); + printf("\nOther options:\n" + " -h, --help display this help message\n"); + exit(EXIT_SUCCESS); +} + +/* Command helper functions. */ + +static struct json * +parse_json(const char *s) +{ + struct json *json = json_from_string(s); + if (json->type == JSON_STRING) { + ovs_fatal(0, "\"%s\": %s", s, json->u.string); + } + return json; +} + +static void +print_and_free_json(struct json *json) +{ + char *string = json_to_string(json, JSSF_SORT); + json_destroy(json); + puts(string); + free(string); +} + +/* Command implementations. */ + +static void +handle_rpc(struct jsonrpc *rpc, struct jsonrpc_msg *msg, bool *done) +{ + struct jsonrpc_msg *reply = NULL; + if (msg->type == JSONRPC_REQUEST) { + if (!strcmp(msg->method, "echo")) { + reply = jsonrpc_create_reply(json_clone(msg->params), msg->id); + } else { + struct json *error = json_object_create(); + json_object_put_string(error, "error", "unknown method"); + reply = jsonrpc_create_error(error, msg->id); + ovs_error(0, "unknown request %s", msg->method); + } + + } else if (msg->type == JSONRPC_NOTIFY) { + if (!strcmp(msg->method, "shutdown")) { + *done = true; + } else { + jsonrpc_error(rpc, ENOTTY); + ovs_error(0, "unknown notification %s", msg->method); + } + } else { + jsonrpc_error(rpc, EPROTO); + ovs_error(0, "unsolicited JSON-RPC reply or error"); + } + + if (reply) { + jsonrpc_send(rpc, reply); + } +} + +static void - do_listen(int argc UNUSED, char *argv[]) ++do_listen(int argc OVS_UNUSED, char *argv[]) +{ + struct pstream *pstream; + struct jsonrpc **rpcs; + size_t n_rpcs, allocated_rpcs; + bool done; + int error; + + die_if_already_running(); + + error = pstream_open(argv[1], &pstream); + if (error) { + ovs_fatal(error, "could not listen on \"%s\"", argv[1]); + } + + daemonize(); + + rpcs = NULL; + n_rpcs = allocated_rpcs = 0; + done = false; + for (;;) { + struct stream *stream; + size_t i; + + /* Accept new connections. */ + error = pstream_accept(pstream, &stream); + if (!error) { + if (n_rpcs >= allocated_rpcs) { + rpcs = x2nrealloc(rpcs, &allocated_rpcs, sizeof *rpcs); + } + rpcs[n_rpcs++] = jsonrpc_open(stream); + } else if (error != EAGAIN) { + ovs_fatal(error, "pstream_accept failed"); + } + + /* Service existing connections. */ + for (i = 0; i < n_rpcs; ) { + struct jsonrpc *rpc = rpcs[i]; + struct jsonrpc_msg *msg; + + jsonrpc_run(rpc); + if (!jsonrpc_get_backlog(rpc)) { + error = jsonrpc_recv(rpc, &msg); + if (!error) { + handle_rpc(rpc, msg, &done); + jsonrpc_msg_destroy(msg); + } + } + + error = jsonrpc_get_status(rpc); + if (error) { + jsonrpc_close(rpc); + ovs_error(error, "connection closed"); + memmove(&rpcs[i], &rpcs[i + 1], + (n_rpcs - i - 1) * sizeof *rpcs); + n_rpcs--; + } else { + i++; + } + } + + /* Wait for something to do. */ + if (done && !n_rpcs) { + break; + } + pstream_wait(pstream); + for (i = 0; i < n_rpcs; i++) { + struct jsonrpc *rpc = rpcs[i]; + + jsonrpc_wait(rpc); + if (!jsonrpc_get_backlog(rpc)) { + jsonrpc_recv_wait(rpc); + } + } + poll_block(); + } + free(rpcs); + pstream_close(pstream); +} + +static void - do_request(int argc UNUSED, char *argv[]) ++do_request(int argc OVS_UNUSED, char *argv[]) +{ + struct jsonrpc_msg *msg; + struct jsonrpc *rpc; + struct json *params; + struct stream *stream; + const char *method; + char *string; + int error; + + method = argv[2]; + params = parse_json(argv[3]); + msg = jsonrpc_create_request(method, params, NULL); + string = jsonrpc_msg_is_valid(msg); + if (string) { + ovs_fatal(0, "not a valid JSON-RPC request: %s", string); + } + + error = stream_open_block(argv[1], &stream); + if (error) { + ovs_fatal(error, "could not open \"%s\"", argv[1]); + } + rpc = jsonrpc_open(stream); + + error = jsonrpc_send(rpc, msg); + if (error) { + ovs_fatal(error, "could not send request"); + } + + error = jsonrpc_recv_block(rpc, &msg); + if (error) { + ovs_fatal(error, "error waiting for reply"); + } + print_and_free_json(jsonrpc_msg_to_json(msg)); + + jsonrpc_close(rpc); +} + +static void - do_notify(int argc UNUSED, char *argv[]) ++do_notify(int argc OVS_UNUSED, char *argv[]) +{ + struct jsonrpc_msg *msg; + struct jsonrpc *rpc; + struct json *params; + struct stream *stream; + const char *method; + char *string; + int error; + + method = argv[2]; + params = parse_json(argv[3]); + msg = jsonrpc_create_notify(method, params); + string = jsonrpc_msg_is_valid(msg); + if (string) { + ovs_fatal(0, "not a JSON RPC-valid notification: %s", string); + } + + error = stream_open_block(argv[1], &stream); + if (error) { + ovs_fatal(error, "could not open \"%s\"", argv[1]); + } + rpc = jsonrpc_open(stream); + + error = jsonrpc_send_block(rpc, msg); + if (error) { + ovs_fatal(error, "could not send request"); + } + jsonrpc_close(rpc); +} + +static void - do_help(int argc UNUSED, char *argv[] UNUSED) ++do_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + usage(); +} + +static struct command all_commands[] = { + { "listen", 1, 1, do_listen }, + { "request", 3, 3, do_request }, + { "notify", 3, 3, do_notify }, + { "help", 0, INT_MAX, do_help }, + { NULL, 0, 0, NULL }, +}; diff --cc tests/test-ovsdb.c index 3025ce39,00000000..2e12d495 mode 100644,000000..100644 --- a/tests/test-ovsdb.c +++ b/tests/test-ovsdb.c @@@ -1,1865 -1,0 +1,1865 @@@ +/* + * Copyright (c) 2009, 2010 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "command-line.h" +#include "dynamic-string.h" +#include "json.h" +#include "jsonrpc.h" +#include "ovsdb-data.h" +#include "ovsdb-error.h" +#include "ovsdb-idl.h" +#include "ovsdb-types.h" +#include "ovsdb/column.h" +#include "ovsdb/condition.h" +#include "ovsdb/file.h" +#include "ovsdb/log.h" +#include "ovsdb/mutation.h" +#include "ovsdb/ovsdb.h" +#include "ovsdb/query.h" +#include "ovsdb/row.h" +#include "ovsdb/table.h" +#include "ovsdb/transaction.h" +#include "ovsdb/trigger.h" +#include "poll-loop.h" +#include "stream.h" +#include "svec.h" +#include "tests/idltest.h" +#include "timeval.h" +#include "util.h" +#include "vlog.h" + +static struct command all_commands[]; + +static void usage(void) NO_RETURN; +static void parse_options(int argc, char *argv[]); + +int +main(int argc, char *argv[]) +{ + set_program_name(argv[0]); + time_init(); + vlog_init(); + parse_options(argc, argv); + run_command(argc - optind, argv + optind, all_commands); + return 0; +} + +static void +parse_options(int argc, char *argv[]) +{ + static struct option long_options[] = { + {"timeout", required_argument, 0, 't'}, + {"verbose", optional_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0}, + }; + char *short_options = long_options_to_short_options(long_options); + + for (;;) { + unsigned long int timeout; + int c; + + c = getopt_long(argc, argv, short_options, long_options, NULL); + if (c == -1) { + break; + } + + switch (c) { + case 't': + timeout = strtoul(optarg, NULL, 10); + if (timeout <= 0) { + ovs_fatal(0, "value %s on -t or --timeout is not at least 1", + optarg); + } else { + time_alarm(timeout); + } + break; + + case 'h': + usage(); + + case 'v': + vlog_set_verbosity(optarg); + break; + + case '?': + exit(EXIT_FAILURE); + + default: + abort(); + } + } + free(short_options); +} + +static void +usage(void) +{ + printf("%s: Open vSwitch database test utility\n" + "usage: %s [OPTIONS] COMMAND [ARG...]\n\n" + " log-io FILE FLAGS COMMAND...\n" + " open FILE with FLAGS, run COMMANDs\n" + " parse-atomic-type TYPE\n" + " parse TYPE as OVSDB atomic type, and re-serialize\n" + " parse-base-type TYPE\n" + " parse TYPE as OVSDB base type, and re-serialize\n" + " parse-type JSON\n" + " parse JSON as OVSDB type, and re-serialize\n" + " parse-atoms TYPE ATOM...\n" + " parse JSON ATOMs as atoms of TYPE, and re-serialize\n" + " parse-atom-strings TYPE ATOM...\n" + " parse string ATOMs as atoms of given TYPE, and re-serialize\n" + " sort-atoms TYPE ATOM...\n" + " print JSON ATOMs in sorted order\n" + " parse-data TYPE DATUM...\n" + " parse JSON DATUMs as data of given TYPE, and re-serialize\n" + " parse-data-strings TYPE DATUM...\n" + " parse string DATUMs as data of given TYPE, and re-serialize\n" + " parse-column NAME OBJECT\n" + " parse column NAME with info OBJECT, and re-serialize\n" + " parse-table NAME OBJECT\n" + " parse table NAME with info OBJECT\n" + " parse-row TABLE ROW..., and re-serialize\n" + " parse each ROW of defined TABLE\n" + " compare-row TABLE ROW...\n" + " mutually compare all of the ROWs, print those that are equal\n" + " parse-conditions TABLE CONDITION...\n" + " parse each CONDITION on TABLE, and re-serialize\n" + " evaluate-conditions TABLE [CONDITION,...] [ROW,...]\n" + " test CONDITIONS on TABLE against each ROW, print results\n" + " parse-mutations TABLE MUTATION...\n" + " parse each MUTATION on TABLE, and re-serialize\n" + " execute-mutations TABLE [MUTATION,...] [ROW,...]\n" + " execute MUTATIONS on TABLE on each ROW, print results\n" + " query TABLE [ROW,...] [CONDITION,...]\n" + " add each ROW to TABLE, then query and print the rows that\n" + " satisfy each CONDITION.\n" + " query-distinct TABLE [ROW,...] [CONDITION,...] COLUMNS\n" + " add each ROW to TABLE, then query and print the rows that\n" + " satisfy each CONDITION and have distinct COLUMNS.\n" + " parse-schema JSON\n" + " parse JSON as an OVSDB schema, and re-serialize\n" + " transact COMMAND\n" + " execute each specified transactional COMMAND:\n" + " commit\n" + " abort\n" + " insert UUID I J\n" + " delete UUID\n" + " modify UUID I J\n" + " print\n" + " execute SCHEMA TRANSACTION...\n" + " executes each TRANSACTION on an initially empty database\n" + " the specified SCHEMA\n" + " trigger SCHEMA TRANSACTION...\n" + " executes each TRANSACTION on an initially empty database\n" + " the specified SCHEMA. A TRANSACTION of the form\n" + " [\"advance\", NUMBER] advances NUMBER milliseconds in\n" + " simulated time, for causing triggers to time out.\n" + " idl SERVER [TRANSACTION...]\n" + " connect to SERVER and dump the contents of the database\n" + " as seen initially by the IDL implementation and after\n" + " executing each TRANSACTION. (Each TRANSACTION must modify\n" + " the database or this command will hang.)\n", + program_name, program_name); + vlog_usage(); + printf("\nOther options:\n" + " -t, --timeout=SECS give up after SECS seconds\n" + " -h, --help display this help message\n"); + exit(EXIT_SUCCESS); +} + +/* Command helper functions. */ + +static struct json * +parse_json(const char *s) +{ + struct json *json = json_from_string(s); + if (json->type == JSON_STRING) { + ovs_fatal(0, "\"%s\": %s", s, json->u.string); + } + return json; +} + +static struct json * +unbox_json(struct json *json) +{ + if (json->type == JSON_ARRAY && json->u.array.n == 1) { + struct json *inner = json->u.array.elems[0]; + json->u.array.elems[0] = NULL; + json_destroy(json); + return inner; + } else { + return json; + } +} + +static void +print_and_free_json(struct json *json) +{ + char *string = json_to_string(json, JSSF_SORT); + json_destroy(json); + puts(string); + free(string); +} + +static void +print_and_free_ovsdb_error(struct ovsdb_error *error) +{ + char *string = ovsdb_error_to_string(error); + ovsdb_error_destroy(error); + puts(string); + free(string); +} + +static void +check_ovsdb_error(struct ovsdb_error *error) +{ + if (error) { + char *s = ovsdb_error_to_string(error); + ovsdb_error_destroy(error); + ovs_fatal(0, "%s", s); + } +} + +static void +die_if_error(char *error) +{ + if (error) { + ovs_fatal(0, "%s", error); + } +} + +/* Command implementations. */ + +static void +do_log_io(int argc, char *argv[]) +{ + const char *name = argv[1]; + char *mode = argv[2]; + + struct ovsdb_error *error; + struct ovsdb_log *log; + char *save_ptr = NULL; + const char *token; + int flags; + int i; + + for (flags = 0, token = strtok_r(mode, " |", &save_ptr); token != NULL; + token = strtok_r(NULL, " |", &save_ptr)) + { + if (!strcmp(token, "O_RDONLY")) { + flags |= O_RDONLY; + } else if (!strcmp(token, "O_RDWR")) { + flags |= O_RDWR; + } else if (!strcmp(token, "O_TRUNC")) { + flags |= O_TRUNC; + } else if (!strcmp(token, "O_CREAT")) { + flags |= O_CREAT; + } else if (!strcmp(token, "O_EXCL")) { + flags |= O_EXCL; + } else if (!strcmp(token, "O_TRUNC")) { + flags |= O_TRUNC; + } + } + + check_ovsdb_error(ovsdb_log_open(name, flags, &log)); + printf("%s: open successful\n", name); + + for (i = 3; i < argc; i++) { + const char *command = argv[i]; + if (!strcmp(command, "read")) { + struct json *json; + + error = ovsdb_log_read(log, &json); + if (!error) { + printf("%s: read: ", name); + if (json) { + print_and_free_json(json); + } else { + printf("end of log\n"); + } + continue; + } + } else if (!strncmp(command, "write:", 6)) { + struct json *json = parse_json(command + 6); + error = ovsdb_log_write(log, json); + json_destroy(json); + } else if (!strcmp(command, "commit")) { + error = ovsdb_log_commit(log); + } else { + ovs_fatal(0, "unknown log-io command \"%s\"", command); + } + if (error) { + char *s = ovsdb_error_to_string(error); + printf("%s: %s failed: %s\n", name, command, s); + free(s); + ovsdb_error_destroy(error); + } else { + printf("%s: %s successful\n", name, command); + } + } + + ovsdb_log_close(log); +} + +static void - do_parse_atomic_type(int argc UNUSED, char *argv[]) ++do_parse_atomic_type(int argc OVS_UNUSED, char *argv[]) +{ + enum ovsdb_atomic_type type; + struct json *json; + + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_atomic_type_from_json(&type, json)); + json_destroy(json); + print_and_free_json(ovsdb_atomic_type_to_json(type)); +} + +static void - do_parse_base_type(int argc UNUSED, char *argv[]) ++do_parse_base_type(int argc OVS_UNUSED, char *argv[]) +{ + struct ovsdb_base_type base; + struct json *json; + + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_base_type_from_json(&base, json)); + json_destroy(json); + print_and_free_json(ovsdb_base_type_to_json(&base)); + ovsdb_base_type_destroy(&base); +} + +static void - do_parse_type(int argc UNUSED, char *argv[]) ++do_parse_type(int argc OVS_UNUSED, char *argv[]) +{ + struct ovsdb_type type; + struct json *json; + + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_type_from_json(&type, json)); + json_destroy(json); + print_and_free_json(ovsdb_type_to_json(&type)); + ovsdb_type_destroy(&type); +} + +static void +do_parse_atoms(int argc, char *argv[]) +{ + struct ovsdb_base_type base; + struct json *json; + int i; + + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_base_type_from_json(&base, json)); + json_destroy(json); + + for (i = 2; i < argc; i++) { + struct ovsdb_error *error; + union ovsdb_atom atom; + + json = unbox_json(parse_json(argv[i])); + error = ovsdb_atom_from_json(&atom, &base, json, NULL); + json_destroy(json); + + if (error) { + print_and_free_ovsdb_error(error); + } else { + print_and_free_json(ovsdb_atom_to_json(&atom, base.type)); + ovsdb_atom_destroy(&atom, base.type); + } + } + ovsdb_base_type_destroy(&base); +} + +static void +do_parse_atom_strings(int argc, char *argv[]) +{ + struct ovsdb_base_type base; + struct json *json; + int i; + + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_base_type_from_json(&base, json)); + json_destroy(json); + + for (i = 2; i < argc; i++) { + union ovsdb_atom atom; + struct ds out; + + die_if_error(ovsdb_atom_from_string(&atom, &base, argv[i])); + + ds_init(&out); + ovsdb_atom_to_string(&atom, base.type, &out); + puts(ds_cstr(&out)); + ds_destroy(&out); + + ovsdb_atom_destroy(&atom, base.type); + } + ovsdb_base_type_destroy(&base); +} + +static void +do_parse_data(int argc, char *argv[]) +{ + struct ovsdb_type type; + struct json *json; + int i; + + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_type_from_json(&type, json)); + json_destroy(json); + + for (i = 2; i < argc; i++) { + struct ovsdb_datum datum; + + json = unbox_json(parse_json(argv[i])); + check_ovsdb_error(ovsdb_datum_from_json(&datum, &type, json, NULL)); + json_destroy(json); + + print_and_free_json(ovsdb_datum_to_json(&datum, &type)); + + ovsdb_datum_destroy(&datum, &type); + } + ovsdb_type_destroy(&type); +} + +static void +do_parse_data_strings(int argc, char *argv[]) +{ + struct ovsdb_type type; + struct json *json; + int i; + + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_type_from_json(&type, json)); + json_destroy(json); + + for (i = 2; i < argc; i++) { + struct ovsdb_datum datum; + struct ds out; + + die_if_error(ovsdb_datum_from_string(&datum, &type, argv[i])); + + ds_init(&out); + ovsdb_datum_to_string(&datum, &type, &out); + puts(ds_cstr(&out)); + ds_destroy(&out); + + ovsdb_datum_destroy(&datum, &type); + } + ovsdb_type_destroy(&type); +} + +static enum ovsdb_atomic_type compare_atoms_atomic_type; + +static int +compare_atoms(const void *a_, const void *b_) +{ + const union ovsdb_atom *a = a_; + const union ovsdb_atom *b = b_; + + return ovsdb_atom_compare_3way(a, b, compare_atoms_atomic_type); +} + +static void - do_sort_atoms(int argc UNUSED, char *argv[]) ++do_sort_atoms(int argc OVS_UNUSED, char *argv[]) +{ + struct ovsdb_base_type base; + union ovsdb_atom *atoms; + struct json *json, **json_atoms; + size_t n_atoms; + int i; + + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_base_type_from_json(&base, json)); + json_destroy(json); + + json = unbox_json(parse_json(argv[2])); + if (json->type != JSON_ARRAY) { + ovs_fatal(0, "second argument must be array"); + } + + /* Convert JSON atoms to internal representation. */ + n_atoms = json->u.array.n; + atoms = xmalloc(n_atoms * sizeof *atoms); + for (i = 0; i < n_atoms; i++) { + check_ovsdb_error(ovsdb_atom_from_json(&atoms[i], &base, + json->u.array.elems[i], NULL)); + } + json_destroy(json); + + /* Sort atoms. */ + compare_atoms_atomic_type = base.type; + qsort(atoms, n_atoms, sizeof *atoms, compare_atoms); + + /* Convert internal representation back to JSON. */ + json_atoms = xmalloc(n_atoms * sizeof *json_atoms); + for (i = 0; i < n_atoms; i++) { + json_atoms[i] = ovsdb_atom_to_json(&atoms[i], base.type); + ovsdb_atom_destroy(&atoms[i], base.type); + } + print_and_free_json(json_array_create(json_atoms, n_atoms)); + free(atoms); + ovsdb_base_type_destroy(&base); +} + +static void - do_parse_column(int argc UNUSED, char *argv[]) ++do_parse_column(int argc OVS_UNUSED, char *argv[]) +{ + struct ovsdb_column *column; + struct json *json; + + json = parse_json(argv[2]); + check_ovsdb_error(ovsdb_column_from_json(json, argv[1], &column)); + json_destroy(json); + print_and_free_json(ovsdb_column_to_json(column)); + ovsdb_column_destroy(column); +} + +static void - do_parse_table(int argc UNUSED, char *argv[]) ++do_parse_table(int argc OVS_UNUSED, char *argv[]) +{ + struct ovsdb_table_schema *ts; + struct json *json; + + json = parse_json(argv[2]); + check_ovsdb_error(ovsdb_table_schema_from_json(json, argv[1], &ts)); + json_destroy(json); + print_and_free_json(ovsdb_table_schema_to_json(ts)); + ovsdb_table_schema_destroy(ts); +} + +static void +do_parse_rows(int argc, char *argv[]) +{ + struct ovsdb_column_set all_columns; + struct ovsdb_table_schema *ts; + struct ovsdb_table *table; + struct json *json; + int i; + + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts)); + json_destroy(json); + + table = ovsdb_table_create(ts); + ovsdb_column_set_init(&all_columns); + ovsdb_column_set_add_all(&all_columns, table); + + for (i = 2; i < argc; i++) { + struct ovsdb_column_set columns; + struct ovsdb_row *row; + + ovsdb_column_set_init(&columns); + row = ovsdb_row_create(table); + + json = unbox_json(parse_json(argv[i])); + check_ovsdb_error(ovsdb_row_from_json(row, json, NULL, &columns)); + json_destroy(json); + + print_and_free_json(ovsdb_row_to_json(row, &all_columns)); + + if (columns.n_columns) { + struct svec names; + size_t j; + char *s; + + svec_init(&names); + for (j = 0; j < columns.n_columns; j++) { + svec_add(&names, columns.columns[j]->name); + } + svec_sort(&names); + s = svec_join(&names, ", ", ""); + puts(s); + free(s); + svec_destroy(&names); + } else { + printf("\n"); + } + + ovsdb_column_set_destroy(&columns); + ovsdb_row_destroy(row); + } + + ovsdb_column_set_destroy(&all_columns); + ovsdb_table_destroy(table); /* Also destroys 'ts'. */ +} + +static void +do_compare_rows(int argc, char *argv[]) +{ + struct ovsdb_column_set all_columns; + struct ovsdb_table_schema *ts; + struct ovsdb_table *table; + struct ovsdb_row **rows; + struct json *json; + char **names; + int n_rows; + int i, j; + + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts)); + json_destroy(json); + + table = ovsdb_table_create(ts); + ovsdb_column_set_init(&all_columns); + ovsdb_column_set_add_all(&all_columns, table); + + n_rows = argc - 2; + rows = xmalloc(sizeof *rows * n_rows); + names = xmalloc(sizeof *names * n_rows); + for (i = 0; i < n_rows; i++) { + rows[i] = ovsdb_row_create(table); + + json = parse_json(argv[i + 2]); + if (json->type != JSON_ARRAY || json->u.array.n != 2 + || json->u.array.elems[0]->type != JSON_STRING) { + ovs_fatal(0, "\"%s\" does not have expected form " + "[\"name\", {data}]", argv[i]); + } + names[i] = xstrdup(json->u.array.elems[0]->u.string); + check_ovsdb_error(ovsdb_row_from_json(rows[i], json->u.array.elems[1], + NULL, NULL)); + json_destroy(json); + } + for (i = 0; i < n_rows; i++) { + uint32_t i_hash = ovsdb_row_hash_columns(rows[i], &all_columns, 0); + for (j = i + 1; j < n_rows; j++) { + uint32_t j_hash = ovsdb_row_hash_columns(rows[j], &all_columns, 0); + if (ovsdb_row_equal_columns(rows[i], rows[j], &all_columns)) { + printf("%s == %s\n", names[i], names[j]); + if (i_hash != j_hash) { + printf("but hash(%s) != hash(%s)\n", names[i], names[j]); + abort(); + } + } else if (i_hash == j_hash) { + printf("hash(%s) == hash(%s)\n", names[i], names[j]); + } + } + } + for (i = 0; i < n_rows; i++) { + ovsdb_row_destroy(rows[i]); + free(names[i]); + } + free(rows); + free(names); + + ovsdb_column_set_destroy(&all_columns); + ovsdb_table_destroy(table); /* Also destroys 'ts'. */ +} + +static void +do_parse_conditions(int argc, char *argv[]) +{ + struct ovsdb_table_schema *ts; + struct json *json; + int exit_code = 0; + int i; + + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts)); + json_destroy(json); + + for (i = 2; i < argc; i++) { + struct ovsdb_condition cnd; + struct ovsdb_error *error; + + json = parse_json(argv[i]); + error = ovsdb_condition_from_json(ts, json, NULL, &cnd); + if (!error) { + print_and_free_json(ovsdb_condition_to_json(&cnd)); + } else { + char *s = ovsdb_error_to_string(error); + ovs_error(0, "%s", s); + free(s); + ovsdb_error_destroy(error); + exit_code = 1; + } + json_destroy(json); + + ovsdb_condition_destroy(&cnd); + } + ovsdb_table_schema_destroy(ts); + + exit(exit_code); +} + +static void - do_evaluate_conditions(int argc UNUSED, char *argv[]) ++do_evaluate_conditions(int argc OVS_UNUSED, char *argv[]) +{ + struct ovsdb_table_schema *ts; + struct ovsdb_table *table; + struct ovsdb_condition *conditions; + size_t n_conditions; + struct ovsdb_row **rows; + size_t n_rows; + struct json *json; + size_t i, j; + + /* Parse table schema, create table. */ + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts)); + json_destroy(json); + + table = ovsdb_table_create(ts); + + /* Parse conditions. */ + json = parse_json(argv[2]); + if (json->type != JSON_ARRAY) { + ovs_fatal(0, "CONDITION argument is not JSON array"); + } + n_conditions = json->u.array.n; + conditions = xmalloc(n_conditions * sizeof *conditions); + for (i = 0; i < n_conditions; i++) { + check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i], + NULL, &conditions[i])); + } + json_destroy(json); + + /* Parse rows. */ + json = parse_json(argv[3]); + if (json->type != JSON_ARRAY) { + ovs_fatal(0, "ROW argument is not JSON array"); + } + n_rows = json->u.array.n; + rows = xmalloc(n_rows * sizeof *rows); + for (i = 0; i < n_rows; i++) { + rows[i] = ovsdb_row_create(table); + check_ovsdb_error(ovsdb_row_from_json(rows[i], json->u.array.elems[i], + NULL, NULL)); + } + json_destroy(json); + + for (i = 0; i < n_conditions; i++) { + printf("condition %2d:", i); + for (j = 0; j < n_rows; j++) { + bool result = ovsdb_condition_evaluate(rows[j], &conditions[i]); + if (j % 5 == 0) { + putchar(' '); + } + putchar(result ? 'T' : '-'); + } + printf("\n"); + } + + for (i = 0; i < n_conditions; i++) { + ovsdb_condition_destroy(&conditions[i]); + } + free(conditions); + for (i = 0; i < n_rows; i++) { + ovsdb_row_destroy(rows[i]); + } + free(rows); + ovsdb_table_destroy(table); /* Also destroys 'ts'. */ +} + +static void +do_parse_mutations(int argc, char *argv[]) +{ + struct ovsdb_table_schema *ts; + struct json *json; + int exit_code = 0; + int i; + + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts)); + json_destroy(json); + + for (i = 2; i < argc; i++) { + struct ovsdb_mutation_set set; + struct ovsdb_error *error; + + json = parse_json(argv[i]); + error = ovsdb_mutation_set_from_json(ts, json, NULL, &set); + if (!error) { + print_and_free_json(ovsdb_mutation_set_to_json(&set)); + } else { + char *s = ovsdb_error_to_string(error); + ovs_error(0, "%s", s); + free(s); + ovsdb_error_destroy(error); + exit_code = 1; + } + json_destroy(json); + + ovsdb_mutation_set_destroy(&set); + } + ovsdb_table_schema_destroy(ts); + + exit(exit_code); +} + +static void - do_execute_mutations(int argc UNUSED, char *argv[]) ++do_execute_mutations(int argc OVS_UNUSED, char *argv[]) +{ + struct ovsdb_table_schema *ts; + struct ovsdb_table *table; + struct ovsdb_mutation_set *sets; + size_t n_sets; + struct ovsdb_row **rows; + size_t n_rows; + struct json *json; + size_t i, j; + + /* Parse table schema, create table. */ + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts)); + json_destroy(json); + + table = ovsdb_table_create(ts); + + /* Parse mutations. */ + json = parse_json(argv[2]); + if (json->type != JSON_ARRAY) { + ovs_fatal(0, "MUTATION argument is not JSON array"); + } + n_sets = json->u.array.n; + sets = xmalloc(n_sets * sizeof *sets); + for (i = 0; i < n_sets; i++) { + check_ovsdb_error(ovsdb_mutation_set_from_json(ts, + json->u.array.elems[i], + NULL, &sets[i])); + } + json_destroy(json); + + /* Parse rows. */ + json = parse_json(argv[3]); + if (json->type != JSON_ARRAY) { + ovs_fatal(0, "ROW argument is not JSON array"); + } + n_rows = json->u.array.n; + rows = xmalloc(n_rows * sizeof *rows); + for (i = 0; i < n_rows; i++) { + rows[i] = ovsdb_row_create(table); + check_ovsdb_error(ovsdb_row_from_json(rows[i], json->u.array.elems[i], + NULL, NULL)); + } + json_destroy(json); + + for (i = 0; i < n_sets; i++) { + printf("mutation %2d:\n", i); + for (j = 0; j < n_rows; j++) { + struct ovsdb_error *error; + struct ovsdb_row *row; + + row = ovsdb_row_clone(rows[j]); + error = ovsdb_mutation_set_execute(row, &sets[i]); + + printf("row %zu: ", j); + if (error) { + print_and_free_ovsdb_error(error); + } else { + struct ovsdb_column_set columns; + struct shash_node *node; + + ovsdb_column_set_init(&columns); + SHASH_FOR_EACH (node, &ts->columns) { + struct ovsdb_column *c = node->data; + if (!ovsdb_datum_equals(&row->fields[c->index], + &rows[j]->fields[c->index], + &c->type)) { + ovsdb_column_set_add(&columns, c); + } + } + if (columns.n_columns) { + print_and_free_json(ovsdb_row_to_json(row, &columns)); + } else { + printf("no change\n"); + } + ovsdb_column_set_destroy(&columns); + } + ovsdb_row_destroy(row); + } + printf("\n"); + } + + for (i = 0; i < n_sets; i++) { + ovsdb_mutation_set_destroy(&sets[i]); + } + free(sets); + for (i = 0; i < n_rows; i++) { + ovsdb_row_destroy(rows[i]); + } + free(rows); + ovsdb_table_destroy(table); /* Also destroys 'ts'. */ +} + +struct do_query_cbdata { + struct uuid *row_uuids; + int *counts; + size_t n_rows; +}; + +static bool +do_query_cb(const struct ovsdb_row *row, void *cbdata_) +{ + struct do_query_cbdata *cbdata = cbdata_; + size_t i; + + for (i = 0; i < cbdata->n_rows; i++) { + if (uuid_equals(ovsdb_row_get_uuid(row), &cbdata->row_uuids[i])) { + cbdata->counts[i]++; + } + } + + return true; +} + +static void - do_query(int argc UNUSED, char *argv[]) ++do_query(int argc OVS_UNUSED, char *argv[]) +{ + struct do_query_cbdata cbdata; + struct ovsdb_table_schema *ts; + struct ovsdb_table *table; + struct json *json; + int exit_code = 0; + size_t i; + + /* Parse table schema, create table. */ + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts)); + json_destroy(json); + + table = ovsdb_table_create(ts); + + /* Parse rows, add to table. */ + json = parse_json(argv[2]); + if (json->type != JSON_ARRAY) { + ovs_fatal(0, "ROW argument is not JSON array"); + } + cbdata.n_rows = json->u.array.n; + cbdata.row_uuids = xmalloc(cbdata.n_rows * sizeof *cbdata.row_uuids); + cbdata.counts = xmalloc(cbdata.n_rows * sizeof *cbdata.counts); + for (i = 0; i < cbdata.n_rows; i++) { + struct ovsdb_row *row = ovsdb_row_create(table); + uuid_generate(ovsdb_row_get_uuid_rw(row)); + check_ovsdb_error(ovsdb_row_from_json(row, json->u.array.elems[i], + NULL, NULL)); + if (ovsdb_table_get_row(table, ovsdb_row_get_uuid(row))) { + ovs_fatal(0, "duplicate UUID "UUID_FMT" in table", + UUID_ARGS(ovsdb_row_get_uuid(row))); + } + cbdata.row_uuids[i] = *ovsdb_row_get_uuid(row); + ovsdb_table_put_row(table, row); + } + json_destroy(json); + + /* Parse conditions and execute queries. */ + json = parse_json(argv[3]); + if (json->type != JSON_ARRAY) { + ovs_fatal(0, "CONDITION argument is not JSON array"); + } + for (i = 0; i < json->u.array.n; i++) { + struct ovsdb_condition cnd; + size_t j; + + check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i], + NULL, &cnd)); + + memset(cbdata.counts, 0, cbdata.n_rows * sizeof *cbdata.counts); + ovsdb_query(table, &cnd, do_query_cb, &cbdata); + + printf("query %2d:", i); + for (j = 0; j < cbdata.n_rows; j++) { + if (j % 5 == 0) { + putchar(' '); + } + if (cbdata.counts[j]) { + printf("%d", cbdata.counts[j]); + if (cbdata.counts[j] > 1) { + /* Dup! */ + exit_code = 1; + } + } else { + putchar('-'); + } + } + putchar('\n'); + + ovsdb_condition_destroy(&cnd); + } + json_destroy(json); + + ovsdb_table_destroy(table); /* Also destroys 'ts'. */ + + exit(exit_code); +} + +struct do_query_distinct_class { + struct ovsdb_row *example; + int count; +}; + +struct do_query_distinct_row { + struct uuid uuid; + struct do_query_distinct_class *class; +}; + +static void - do_query_distinct(int argc UNUSED, char *argv[]) ++do_query_distinct(int argc OVS_UNUSED, char *argv[]) +{ + struct ovsdb_column_set columns; + struct ovsdb_table_schema *ts; + struct ovsdb_table *table; + struct do_query_distinct_row *rows; + size_t n_rows; + struct do_query_distinct_class *classes; + size_t n_classes; + struct json *json; + int exit_code = 0; + size_t i, j, k; + + /* Parse table schema, create table. */ + json = unbox_json(parse_json(argv[1])); + check_ovsdb_error(ovsdb_table_schema_from_json(json, "mytable", &ts)); + json_destroy(json); + + table = ovsdb_table_create(ts); + + /* Parse column set. */ + json = parse_json(argv[4]); + check_ovsdb_error(ovsdb_column_set_from_json(json, table, &columns)); + json_destroy(json); + + /* Parse rows, add to table. */ + json = parse_json(argv[2]); + if (json->type != JSON_ARRAY) { + ovs_fatal(0, "ROW argument is not JSON array"); + } + n_rows = json->u.array.n; + rows = xmalloc(n_rows * sizeof *rows); + classes = xmalloc(n_rows * sizeof *classes); + n_classes = 0; + for (i = 0; i < n_rows; i++) { + struct ovsdb_row *row; + size_t j; + + /* Parse row. */ + row = ovsdb_row_create(table); + uuid_generate(ovsdb_row_get_uuid_rw(row)); + check_ovsdb_error(ovsdb_row_from_json(row, json->u.array.elems[i], + NULL, NULL)); + + /* Initialize row and find equivalence class. */ + rows[i].uuid = *ovsdb_row_get_uuid(row); + rows[i].class = NULL; + for (j = 0; j < n_classes; j++) { + if (ovsdb_row_equal_columns(row, classes[j].example, &columns)) { + rows[i].class = &classes[j]; + break; + } + } + if (!rows[i].class) { + rows[i].class = &classes[n_classes]; + classes[n_classes].example = ovsdb_row_clone(row); + n_classes++; + } + + /* Add row to table. */ + if (ovsdb_table_get_row(table, ovsdb_row_get_uuid(row))) { + ovs_fatal(0, "duplicate UUID "UUID_FMT" in table", + UUID_ARGS(ovsdb_row_get_uuid(row))); + } + ovsdb_table_put_row(table, row); + + } + json_destroy(json); + + /* Parse conditions and execute queries. */ + json = parse_json(argv[3]); + if (json->type != JSON_ARRAY) { + ovs_fatal(0, "CONDITION argument is not JSON array"); + } + for (i = 0; i < json->u.array.n; i++) { + struct ovsdb_row_set results; + struct ovsdb_condition cnd; + + check_ovsdb_error(ovsdb_condition_from_json(ts, json->u.array.elems[i], + NULL, &cnd)); + + for (j = 0; j < n_classes; j++) { + classes[j].count = 0; + } + ovsdb_row_set_init(&results); + ovsdb_query_distinct(table, &cnd, &columns, &results); + for (j = 0; j < results.n_rows; j++) { + for (k = 0; k < n_rows; k++) { + if (uuid_equals(ovsdb_row_get_uuid(results.rows[j]), + &rows[k].uuid)) { + rows[k].class->count++; + } + } + } + ovsdb_row_set_destroy(&results); + + printf("query %2d:", i); + for (j = 0; j < n_rows; j++) { + int count = rows[j].class->count; + + if (j % 5 == 0) { + putchar(' '); + } + if (count > 1) { + /* Dup! */ + printf("%d", count); + exit_code = 1; + } else if (count == 1) { + putchar("abcdefghijklmnopqrstuvwxyz"[rows[j].class - classes]); + } else { + putchar('-'); + } + } + putchar('\n'); + + ovsdb_condition_destroy(&cnd); + } + json_destroy(json); + + ovsdb_table_destroy(table); /* Also destroys 'ts'. */ + + exit(exit_code); +} + +static void - do_parse_schema(int argc UNUSED, char *argv[]) ++do_parse_schema(int argc OVS_UNUSED, char *argv[]) +{ + struct ovsdb_schema *schema; + struct json *json; + + json = parse_json(argv[1]); + check_ovsdb_error(ovsdb_schema_from_json(json, &schema)); + json_destroy(json); + print_and_free_json(ovsdb_schema_to_json(schema)); + ovsdb_schema_destroy(schema); +} + +static void - do_execute(int argc UNUSED, char *argv[]) ++do_execute(int argc OVS_UNUSED, char *argv[]) +{ + struct ovsdb_schema *schema; + struct json *json; + struct ovsdb *db; + int i; + + /* Create database. */ + json = parse_json(argv[1]); + check_ovsdb_error(ovsdb_schema_from_json(json, &schema)); + json_destroy(json); + db = ovsdb_create(schema); + + for (i = 2; i < argc; i++) { + struct json *params, *result; + char *s; + + params = parse_json(argv[i]); + result = ovsdb_execute(db, params, 0, NULL); + s = json_to_string(result, JSSF_SORT); + printf("%s\n", s); + free(s); + json_destroy(params); + json_destroy(result); + } + + ovsdb_destroy(db); +} + +struct test_trigger { + struct ovsdb_trigger trigger; + int number; +}; + +static void +do_trigger_dump(struct test_trigger *t, long long int now, const char *title) +{ + struct json *result; + char *s; + + result = ovsdb_trigger_steal_result(&t->trigger); + s = json_to_string(result, JSSF_SORT); + printf("t=%lld: trigger %d (%s): %s\n", now, t->number, title, s); + free(s); + json_destroy(result); + ovsdb_trigger_destroy(&t->trigger); + free(t); +} + +static void - do_trigger(int argc UNUSED, char *argv[]) ++do_trigger(int argc OVS_UNUSED, char *argv[]) +{ + struct ovsdb_schema *schema; + struct list completions; + struct json *json; + struct ovsdb *db; + long long int now; + int number; + int i; + + /* Create database. */ + json = parse_json(argv[1]); + check_ovsdb_error(ovsdb_schema_from_json(json, &schema)); + json_destroy(json); + db = ovsdb_create(schema); + + list_init(&completions); + now = 0; + number = 0; + for (i = 2; i < argc; i++) { + struct json *params = parse_json(argv[i]); + if (params->type == JSON_ARRAY + && json_array(params)->n == 2 + && json_array(params)->elems[0]->type == JSON_STRING + && !strcmp(json_string(json_array(params)->elems[0]), "advance") + && json_array(params)->elems[1]->type == JSON_INTEGER) { + now += json_integer(json_array(params)->elems[1]); + json_destroy(params); + } else { + struct test_trigger *t = xmalloc(sizeof *t); + ovsdb_trigger_init(db, &t->trigger, params, &completions, now); + t->number = number++; + if (ovsdb_trigger_is_complete(&t->trigger)) { + do_trigger_dump(t, now, "immediate"); + } else { + printf("t=%lld: new trigger %d\n", now, t->number); + } + } + + ovsdb_trigger_run(db, now); + while (!list_is_empty(&completions)) { + do_trigger_dump(CONTAINER_OF(list_pop_front(&completions), + struct test_trigger, trigger.node), + now, "delayed"); + } + + ovsdb_trigger_wait(db, now); + poll_immediate_wake(); + poll_block(); + } + + ovsdb_destroy(db); +} + +static void - do_help(int argc UNUSED, char *argv[] UNUSED) ++do_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + usage(); +} + +/* "transact" command. */ + +static struct ovsdb *do_transact_db; +static struct ovsdb_txn *do_transact_txn; +static struct ovsdb_table *do_transact_table; + +static void - do_transact_commit(int argc UNUSED, char *argv[] UNUSED) ++do_transact_commit(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + ovsdb_txn_commit(do_transact_txn, false); + do_transact_txn = NULL; +} + +static void - do_transact_abort(int argc UNUSED, char *argv[] UNUSED) ++do_transact_abort(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + ovsdb_txn_abort(do_transact_txn); + do_transact_txn = NULL; +} + +static void +uuid_from_integer(int integer, struct uuid *uuid) +{ + uuid_zero(uuid); + uuid->parts[3] = integer; +} + +static const struct ovsdb_row * +do_transact_find_row(const char *uuid_string) +{ + const struct ovsdb_row *row; + struct uuid uuid; + + uuid_from_integer(atoi(uuid_string), &uuid); + row = ovsdb_table_get_row(do_transact_table, &uuid); + if (!row) { + ovs_fatal(0, "table does not contain row with UUID "UUID_FMT, + UUID_ARGS(&uuid)); + } + return row; +} + +static void +do_transact_set_integer(struct ovsdb_row *row, const char *column_name, + int integer) +{ + if (integer != -1) { + const struct ovsdb_column *column; + + column = ovsdb_table_schema_get_column(do_transact_table->schema, + column_name); + row->fields[column->index].keys[0].integer = integer; + } +} + +static int +do_transact_get_integer(const struct ovsdb_row *row, const char *column_name) +{ + const struct ovsdb_column *column; + + column = ovsdb_table_schema_get_column(do_transact_table->schema, + column_name); + return row->fields[column->index].keys[0].integer; +} + +static void +do_transact_set_i_j(struct ovsdb_row *row, + const char *i_string, const char *j_string) +{ + do_transact_set_integer(row, "i", atoi(i_string)); + do_transact_set_integer(row, "j", atoi(j_string)); +} + +static void - do_transact_insert(int argc UNUSED, char *argv[] UNUSED) ++do_transact_insert(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + struct ovsdb_row *row; + struct uuid *uuid; + + row = ovsdb_row_create(do_transact_table); + + /* Set UUID. */ + uuid = ovsdb_row_get_uuid_rw(row); + uuid_from_integer(atoi(argv[1]), uuid); + if (ovsdb_table_get_row(do_transact_table, uuid)) { + ovs_fatal(0, "table already contains row with UUID "UUID_FMT, + UUID_ARGS(uuid)); + } + + do_transact_set_i_j(row, argv[2], argv[3]); + + /* Insert row. */ + ovsdb_txn_row_insert(do_transact_txn, row); +} + +static void - do_transact_delete(int argc UNUSED, char *argv[] UNUSED) ++do_transact_delete(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + const struct ovsdb_row *row = do_transact_find_row(argv[1]); + ovsdb_txn_row_delete(do_transact_txn, row); +} + +static void - do_transact_modify(int argc UNUSED, char *argv[] UNUSED) ++do_transact_modify(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + const struct ovsdb_row *row_ro; + struct ovsdb_row *row_rw; + + row_ro = do_transact_find_row(argv[1]); + row_rw = ovsdb_txn_row_modify(do_transact_txn, row_ro); + do_transact_set_i_j(row_rw, argv[2], argv[3]); +} + +static int +compare_rows_by_uuid(const void *a_, const void *b_) +{ + struct ovsdb_row *const *ap = a_; + struct ovsdb_row *const *bp = b_; + + return uuid_compare_3way(ovsdb_row_get_uuid(*ap), ovsdb_row_get_uuid(*bp)); +} + +static void - do_transact_print(int argc UNUSED, char *argv[] UNUSED) ++do_transact_print(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + const struct ovsdb_row **rows; + const struct ovsdb_row *row; + size_t n_rows; + size_t i; + + n_rows = hmap_count(&do_transact_table->rows); + rows = xmalloc(n_rows * sizeof *rows); + i = 0; + HMAP_FOR_EACH (row, struct ovsdb_row, hmap_node, + &do_transact_table->rows) { + rows[i++] = row; + } + assert(i == n_rows); + + qsort(rows, n_rows, sizeof *rows, compare_rows_by_uuid); + + for (i = 0; i < n_rows; i++) { + printf("\n%"PRId32": i=%d, j=%d", + ovsdb_row_get_uuid(rows[i])->parts[3], + do_transact_get_integer(rows[i], "i"), + do_transact_get_integer(rows[i], "j")); + } + + free(rows); +} + +static void +do_transact(int argc, char *argv[]) +{ + static const struct command do_transact_commands[] = { + { "commit", 0, 0, do_transact_commit }, + { "abort", 0, 0, do_transact_abort }, + { "insert", 2, 3, do_transact_insert }, + { "delete", 1, 1, do_transact_delete }, + { "modify", 2, 3, do_transact_modify }, + { "print", 0, 0, do_transact_print }, + { NULL, 0, 0, NULL }, + }; + + struct ovsdb_schema *schema; + struct json *json; + int i; + + /* Create table. */ + json = parse_json("{\"name\": \"testdb\", " + " \"tables\": " + " {\"mytable\": " + " {\"columns\": " + " {\"i\": {\"type\": \"integer\"}, " + " \"j\": {\"type\": \"integer\"}}}}}"); + check_ovsdb_error(ovsdb_schema_from_json(json, &schema)); + json_destroy(json); + do_transact_db = ovsdb_create(schema); + do_transact_table = ovsdb_get_table(do_transact_db, "mytable"); + assert(do_transact_table != NULL); + + for (i = 1; i < argc; i++) { + struct json *command; + size_t n_args; + char **args; + int j; + + command = parse_json(argv[i]); + if (command->type != JSON_ARRAY) { + ovs_fatal(0, "transaction %d must be JSON array " + "with at least 1 element", i); + } + + n_args = command->u.array.n; + args = xmalloc((n_args + 1) * sizeof *args); + for (j = 0; j < n_args; j++) { + struct json *s = command->u.array.elems[j]; + if (s->type != JSON_STRING) { + ovs_fatal(0, "transaction %d argument %d must be JSON string", + i, j); + } + args[j] = xstrdup(json_string(s)); + } + args[n_args] = NULL; + + if (!do_transact_txn) { + do_transact_txn = ovsdb_txn_create(do_transact_db); + } + + for (j = 0; j < n_args; j++) { + if (j) { + putchar(' '); + } + fputs(args[j], stdout); + } + fputs(":", stdout); + run_command(n_args, args, do_transact_commands); + putchar('\n'); + + for (j = 0; j < n_args; j++) { + free(args[j]); + } + free(args); + json_destroy(command); + } + ovsdb_txn_abort(do_transact_txn); + ovsdb_destroy(do_transact_db); /* Also destroys 'schema'. */ +} + +static int +compare_link1(const void *a_, const void *b_) +{ + const struct idltest_link1 *const *ap = a_; + const struct idltest_link1 *const *bp = b_; + const struct idltest_link1 *a = *ap; + const struct idltest_link1 *b = *bp; + + return a->i < b->i ? -1 : a->i > b->i; +} + +static void +print_idl(struct ovsdb_idl *idl, int step) +{ + const struct idltest_simple *s; + const struct idltest_link1 *l1; + const struct idltest_link2 *l2; + int n = 0; + + IDLTEST_SIMPLE_FOR_EACH (s, idl) { + size_t i; + + printf("%03d: i=%"PRId64" r=%g b=%s s=%s u="UUID_FMT" ia=[", + step, s->i, s->r, s->b ? "true" : "false", + s->s, UUID_ARGS(&s->u)); + for (i = 0; i < s->n_ia; i++) { + printf("%s%"PRId64, i ? " " : "", s->ia[i]); + } + printf("] ra=["); + for (i = 0; i < s->n_ra; i++) { + printf("%s%g", i ? " " : "", s->ra[i]); + } + printf("] ba=["); + for (i = 0; i < s->n_ba; i++) { + printf("%s%s", i ? " " : "", s->ba[i] ? "true" : "false"); + } + printf("] sa=["); + for (i = 0; i < s->n_sa; i++) { + printf("%s%s", i ? " " : "", s->sa[i]); + } + printf("] ua=["); + for (i = 0; i < s->n_ua; i++) { + printf("%s"UUID_FMT, i ? " " : "", UUID_ARGS(&s->ua[i])); + } + printf("] uuid="UUID_FMT"\n", UUID_ARGS(&s->header_.uuid)); + n++; + } + IDLTEST_LINK1_FOR_EACH (l1, idl) { + struct idltest_link1 **links; + size_t i; + + printf("%03d: i=%"PRId64" k=", step, l1->i); + if (l1->k) { + printf("%"PRId64, l1->k->i); + } + printf(" ka=["); + links = xmemdup(l1->ka, l1->n_ka * sizeof *l1->ka); + qsort(links, l1->n_ka, sizeof *links, compare_link1); + for (i = 0; i < l1->n_ka; i++) { + printf("%s%"PRId64, i ? " " : "", links[i]->i); + } + free(links); + printf("] l2="); + if (l1->l2) { + printf("%"PRId64, l1->l2->i); + } + printf(" uuid="UUID_FMT"\n", UUID_ARGS(&l1->header_.uuid)); + n++; + } + IDLTEST_LINK2_FOR_EACH (l2, idl) { + printf("%03d: i=%"PRId64" l1=", step, l2->i); + if (l2->l1) { + printf("%"PRId64, l2->l1->i); + } + printf(" uuid="UUID_FMT"\n", UUID_ARGS(&l2->header_.uuid)); + n++; + } + if (!n) { + printf("%03d: empty\n", step); + } +} + +static unsigned int +print_updated_idl(struct ovsdb_idl *idl, struct jsonrpc *rpc, + int step, unsigned int seqno) +{ + for (;;) { + unsigned int new_seqno; + + if (rpc) { + jsonrpc_run(rpc); + } + ovsdb_idl_run(idl); + new_seqno = ovsdb_idl_get_seqno(idl); + if (new_seqno != seqno) { + print_idl(idl, step); + return new_seqno; + } + + if (rpc) { + jsonrpc_wait(rpc); + } + ovsdb_idl_wait(idl); + poll_block(); + } +} + +static void +parse_uuids(const struct json *json, struct ovsdb_symbol_table *symtab, + size_t *n) +{ + struct uuid uuid; + + if (json->type == JSON_STRING && uuid_from_string(&uuid, json->u.string)) { + char *name = xasprintf("#%d#", *n); + fprintf(stderr, "%s = "UUID_FMT"\n", name, UUID_ARGS(&uuid)); + ovsdb_symbol_table_put(symtab, name, &uuid, false); + free(name); + *n += 1; + } else if (json->type == JSON_ARRAY) { + size_t i; + + for (i = 0; i < json->u.array.n; i++) { + parse_uuids(json->u.array.elems[i], symtab, n); + } + } else if (json->type == JSON_OBJECT) { + const struct shash_node *node; + + SHASH_FOR_EACH (node, json_object(json)) { + parse_uuids(node->data, symtab, n); + } + } +} + +static void +substitute_uuids(struct json *json, const struct ovsdb_symbol_table *symtab) +{ + if (json->type == JSON_STRING) { + const struct ovsdb_symbol *symbol; + + symbol = ovsdb_symbol_table_get(symtab, json->u.string); + if (symbol) { + free(json->u.string); + json->u.string = xasprintf(UUID_FMT, UUID_ARGS(&symbol->uuid)); + } + } else if (json->type == JSON_ARRAY) { + size_t i; + + for (i = 0; i < json->u.array.n; i++) { + substitute_uuids(json->u.array.elems[i], symtab); + } + } else if (json->type == JSON_OBJECT) { + const struct shash_node *node; + + SHASH_FOR_EACH (node, json_object(json)) { + substitute_uuids(node->data, symtab); + } + } +} + +static const struct idltest_simple * +idltest_find_simple(struct ovsdb_idl *idl, int i) +{ + const struct idltest_simple *s; + + IDLTEST_SIMPLE_FOR_EACH (s, idl) { + if (s->i == i) { + return s; + } + } + return NULL; +} + +static void +idl_set(struct ovsdb_idl *idl, char *commands, int step) +{ + char *cmd, *save_ptr1 = NULL; + struct ovsdb_idl_txn *txn; + enum ovsdb_idl_txn_status status; + bool increment = false; + + txn = ovsdb_idl_txn_create(idl); + for (cmd = strtok_r(commands, ",", &save_ptr1); cmd; + cmd = strtok_r(NULL, ",", &save_ptr1)) { + char *save_ptr2 = NULL; + char *name, *arg1, *arg2, *arg3; + + name = strtok_r(cmd, " ", &save_ptr2); + arg1 = strtok_r(NULL, " ", &save_ptr2); + arg2 = strtok_r(NULL, " ", &save_ptr2); + arg3 = strtok_r(NULL, " ", &save_ptr2); + + if (!strcmp(name, "set")) { + const struct idltest_simple *s; + + if (!arg3) { + ovs_fatal(0, "\"set\" command requires 3 arguments"); + } + + s = idltest_find_simple(idl, atoi(arg1)); + if (!s) { + ovs_fatal(0, "\"set\" command asks for nonexistent " + "i=%d", atoi(arg1)); + } + + if (!strcmp(arg2, "b")) { + idltest_simple_set_b(s, atoi(arg3)); + } else if (!strcmp(arg2, "s")) { + idltest_simple_set_s(s, arg3); + } else if (!strcmp(arg2, "u")) { + struct uuid uuid; + uuid_from_string(&uuid, arg3); + idltest_simple_set_u(s, uuid); + } else if (!strcmp(arg2, "r")) { + idltest_simple_set_r(s, atof(arg3)); + } else { + ovs_fatal(0, "\"set\" command asks for unknown column %s", + arg2); + } + } else if (!strcmp(name, "insert")) { + struct idltest_simple *s; + + if (!arg1 || arg2) { + ovs_fatal(0, "\"set\" command requires 1 argument"); + } + + s = idltest_simple_insert(txn); + idltest_simple_set_i(s, atoi(arg1)); + } else if (!strcmp(name, "delete")) { + const struct idltest_simple *s; + + if (!arg1 || arg2) { + ovs_fatal(0, "\"set\" command requires 1 argument"); + } + + s = idltest_find_simple(idl, atoi(arg1)); + if (!s) { + ovs_fatal(0, "\"set\" command asks for nonexistent " + "i=%d", atoi(arg1)); + } + idltest_simple_delete(s); + } else if (!strcmp(name, "increment")) { + if (!arg2 || arg3) { + ovs_fatal(0, "\"set\" command requires 2 arguments"); + } + ovsdb_idl_txn_increment(txn, arg1, arg2, NULL); + increment = true; + } else { + ovs_fatal(0, "unknown command %s", name); + } + } + + while ((status = ovsdb_idl_txn_commit(txn)) == TXN_INCOMPLETE) { + ovsdb_idl_run(idl); + ovsdb_idl_wait(idl); + ovsdb_idl_txn_wait(txn); + poll_block(); + } + printf("%03d: commit, status=%s", + step, ovsdb_idl_txn_status_to_string(status)); + if (increment) { + printf(", increment=%"PRId64, + ovsdb_idl_txn_get_increment_new_value(txn)); + } + putchar('\n'); + ovsdb_idl_txn_destroy(txn); +} + +static void +do_idl(int argc, char *argv[]) +{ + struct jsonrpc *rpc; + struct ovsdb_idl *idl; + unsigned int seqno = 0; + struct ovsdb_symbol_table *symtab; + size_t n_uuids = 0; + int step = 0; + int error; + int i; + + idltest_init(); + + idl = ovsdb_idl_create(argv[1], &idltest_idl_class); + if (argc > 2) { + struct stream *stream; + + error = stream_open_block(argv[1], &stream); + if (error) { + ovs_fatal(error, "failed to connect to \"%s\"", argv[1]); + } + rpc = jsonrpc_open(stream); + } else { + rpc = NULL; + } + + setvbuf(stdout, NULL, _IOLBF, 0); + + symtab = ovsdb_symbol_table_create(); + for (i = 2; i < argc; i++) { + char *arg = argv[i]; + struct jsonrpc_msg *request, *reply; + int error; + + if (*arg == '+') { + /* The previous transaction didn't change anything. */ + arg++; + } else { + seqno = print_updated_idl(idl, rpc, step++, seqno); + } + + if (!strcmp(arg, "reconnect")) { + printf("%03d: reconnect\n", step++); + ovsdb_idl_force_reconnect(idl); + } else if (arg[0] != '[') { + idl_set(idl, arg, step++); + } else { + struct json *json = parse_json(arg); + substitute_uuids(json, symtab); + request = jsonrpc_create_request("transact", json, NULL); + error = jsonrpc_transact_block(rpc, request, &reply); + if (error) { + ovs_fatal(error, "jsonrpc transaction failed"); + } + printf("%03d: ", step++); + if (reply->result) { + parse_uuids(reply->result, symtab, &n_uuids); + } + json_destroy(reply->id); + reply->id = NULL; + print_and_free_json(jsonrpc_msg_to_json(reply)); + } + } + ovsdb_symbol_table_destroy(symtab); + + if (rpc) { + jsonrpc_close(rpc); + } + print_updated_idl(idl, NULL, step++, seqno); + ovsdb_idl_destroy(idl); + printf("%03d: done\n", step); +} + +static struct command all_commands[] = { + { "log-io", 2, INT_MAX, do_log_io }, + { "parse-atomic-type", 1, 1, do_parse_atomic_type }, + { "parse-base-type", 1, 1, do_parse_base_type }, + { "parse-type", 1, 1, do_parse_type }, + { "parse-atoms", 2, INT_MAX, do_parse_atoms }, + { "parse-atom-strings", 2, INT_MAX, do_parse_atom_strings }, + { "parse-data", 2, INT_MAX, do_parse_data }, + { "parse-data-strings", 2, INT_MAX, do_parse_data_strings }, + { "sort-atoms", 2, 2, do_sort_atoms }, + { "parse-column", 2, 2, do_parse_column }, + { "parse-table", 2, 2, do_parse_table }, + { "parse-rows", 2, INT_MAX, do_parse_rows }, + { "compare-rows", 2, INT_MAX, do_compare_rows }, + { "parse-conditions", 2, INT_MAX, do_parse_conditions }, + { "evaluate-conditions", 3, 3, do_evaluate_conditions }, + { "parse-mutations", 2, INT_MAX, do_parse_mutations }, + { "execute-mutations", 3, 3, do_execute_mutations }, + { "query", 3, 3, do_query }, + { "query-distinct", 4, 4, do_query_distinct }, + { "transact", 1, INT_MAX, do_transact }, + { "parse-schema", 1, 1, do_parse_schema }, + { "execute", 2, INT_MAX, do_execute }, + { "trigger", 2, INT_MAX, do_trigger }, + { "idl", 1, INT_MAX, do_idl }, + { "help", 0, INT_MAX, do_help }, + { NULL, 0, 0, NULL }, +}; diff --cc tests/test-reconnect.c index 8441fadd,00000000..93991ff5 mode 100644,000000..100644 --- a/tests/test-reconnect.c +++ b/tests/test-reconnect.c @@@ -1,253 -1,0 +1,253 @@@ +/* - * Copyright (c) 2009 Nicira Networks. ++ * Copyright (c) 2009, 2010 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "reconnect.h" + +#include +#include +#include +#include + +#include "command-line.h" +#include "compiler.h" +#include "svec.h" +#include "util.h" + +static struct reconnect *reconnect; +static int now; + +static const struct command commands[]; + +static void diff_stats(const struct reconnect_stats *old, + const struct reconnect_stats *new); + +int +main(void) +{ + struct reconnect_stats prev; + unsigned int old_max_tries; + int old_time; + char line[128]; + + now = 1000; + reconnect = reconnect_create(now); + reconnect_set_name(reconnect, "remote"); + reconnect_get_stats(reconnect, now, &prev); + printf("### t=%d ###\n", now); + old_time = now; + old_max_tries = reconnect_get_max_tries(reconnect); + while (fgets(line, sizeof line, stdin)) { + struct reconnect_stats cur; + struct svec args; + + fputs(line, stdout); + if (line[0] == '#') { + continue; + } + + svec_init(&args); + svec_parse_words(&args, line); + svec_terminate(&args); + if (!svec_is_empty(&args)) { + run_command(args.n, args.names, commands); + } + svec_destroy(&args); + + if (old_time != now) { + printf("\n### t=%d ###\n", now); + old_time = now; + } + + reconnect_get_stats(reconnect, now, &cur); + diff_stats(&prev, &cur); + prev = cur; + if (reconnect_get_max_tries(reconnect) != old_max_tries) { + old_max_tries = reconnect_get_max_tries(reconnect); + printf(" %u tries left\n", old_max_tries); + } + } + + return 0; +} + +static void - do_enable(int argc UNUSED, char *argv[] UNUSED) ++do_enable(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + reconnect_enable(reconnect, now); +} + +static void - do_disable(int argc UNUSED, char *argv[] UNUSED) ++do_disable(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + reconnect_disable(reconnect, now); +} + +static void - do_force_reconnect(int argc UNUSED, char *argv[] UNUSED) ++do_force_reconnect(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + reconnect_force_reconnect(reconnect, now); +} + +static int +error_from_string(const char *s) +{ + if (!s) { + return 0; + } else if (!strcmp(s, "ECONNREFUSED")) { + return ECONNREFUSED; + } else if (!strcmp(s, "EOF")) { + return EOF; + } else { + ovs_fatal(0, "unknown error '%s'", s); + } +} + +static void - do_disconnected(int argc UNUSED, char *argv[]) ++do_disconnected(int argc OVS_UNUSED, char *argv[]) +{ + reconnect_disconnected(reconnect, now, error_from_string(argv[1])); +} + +static void - do_connecting(int argc UNUSED, char *argv[] UNUSED) ++do_connecting(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + reconnect_connecting(reconnect, now); +} + +static void - do_connect_failed(int argc UNUSED, char *argv[]) ++do_connect_failed(int argc OVS_UNUSED, char *argv[]) +{ + reconnect_connect_failed(reconnect, now, error_from_string(argv[1])); +} + +static void - do_connected(int argc UNUSED, char *argv[] UNUSED) ++do_connected(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + reconnect_connected(reconnect, now); +} + +static void - do_received(int argc UNUSED, char *argv[] UNUSED) ++do_received(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + reconnect_received(reconnect, now); +} + +static void +do_run(int argc, char *argv[]) +{ + enum reconnect_action action; + + if (argc > 1) { + now += atoi(argv[1]); + } + + action = reconnect_run(reconnect, now); + switch (action) { + default: + if (action != 0) { + NOT_REACHED(); + } + break; + + case RECONNECT_CONNECT: + printf(" should connect\n"); + break; + + case RECONNECT_DISCONNECT: + printf(" should disconnect\n"); + break; + + case RECONNECT_PROBE: + printf(" should send probe\n"); + break; + } +} + +static void - do_advance(int argc UNUSED, char *argv[]) ++do_advance(int argc OVS_UNUSED, char *argv[]) +{ + now += atoi(argv[1]); +} + +static void - do_timeout(int argc UNUSED, char *argv[] UNUSED) ++do_timeout(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) +{ + int timeout = reconnect_timeout(reconnect, now); + if (timeout >= 0) { + printf(" advance %d ms\n", timeout); + now += timeout; + } else { + printf(" no timeout\n"); + } +} + +static void - do_set_max_tries(int argc UNUSED, char *argv[]) ++do_set_max_tries(int argc OVS_UNUSED, char *argv[]) +{ + reconnect_set_max_tries(reconnect, atoi(argv[1])); +} + +static void +diff_stats(const struct reconnect_stats *old, + const struct reconnect_stats *new) +{ + if (old->state != new->state + || old->state_elapsed != new->state_elapsed + || old->backoff != new->backoff) { + printf(" in %s for %u ms (%d ms backoff)\n", + new->state, new->state_elapsed, new->backoff); + } + if (old->creation_time != new->creation_time + || old->last_received != new->last_received + || old->last_connected != new->last_connected) { + printf(" created %lld, last received %lld, last connected %lld\n", + new->creation_time, new->last_received, new->last_connected); + } + if (old->n_successful_connections != new->n_successful_connections + || old->n_attempted_connections != new->n_attempted_connections + || old->seqno != new->seqno) { + printf(" %u successful connections out of %u attempts, seqno %u\n", + new->n_successful_connections, new->n_attempted_connections, + new->seqno); + } + if (old->is_connected != new->is_connected + || old->current_connection_duration != new->current_connection_duration + || old->total_connected_duration != new->total_connected_duration) { + printf(" %sconnected (%u ms), total %u ms connected\n", + new->is_connected ? "" : "not ", + new->current_connection_duration, + new->total_connected_duration); + } +} + +static const struct command commands[] = { + { "enable", 0, 0, do_enable }, + { "disable", 0, 0, do_disable }, + { "force-reconnect", 0, 0, do_force_reconnect }, + { "disconnected", 0, 1, do_disconnected }, + { "connecting", 0, 0, do_connecting }, + { "connect-failed", 0, 1, do_connect_failed }, + { "connected", 0, 0, do_connected }, + { "received", 0, 0, do_received }, + { "run", 0, 1, do_run }, + { "advance", 1, 1, do_advance }, + { "timeout", 0, 0, do_timeout }, + { "set-max-tries", 1, 1, do_set_max_tries }, + { NULL, 0, 0, NULL }, +}; + diff --cc tests/test-vconn.c index f12e378f,ff4d9ece..a394f6cf --- a/tests/test-vconn.c +++ b/tests/test-vconn.c @@@ -134,10 -135,8 +134,10 @@@ fpv_destroy(struct fake_pvconn *fpv /* Connects to a fake_pvconn with vconn_open(), then closes the listener and * verifies that vconn_connect() reports 'expected_error'. */ static void - test_refuse_connection(int argc UNUSED, char *argv[]) -test_refuse_connection(const char *type, int expected_error) ++test_refuse_connection(int argc OVS_UNUSED, char *argv[]) { + const char *type = argv[1]; + int expected_error; struct fake_pvconn fpv; struct vconn *vconn; @@@ -156,10 -152,8 +156,10 @@@ * closes it immediately, and verifies that vconn_connect() reports * 'expected_error'. */ static void - test_accept_then_close(int argc UNUSED, char *argv[]) -test_accept_then_close(const char *type, int expected_error) ++test_accept_then_close(int argc OVS_UNUSED, char *argv[]) { + const char *type = argv[1]; + int expected_error; struct fake_pvconn fpv; struct vconn *vconn; @@@ -181,18 -170,17 +181,18 @@@ * reads the hello message from it, then closes the connection and verifies * that vconn_connect() reports 'expected_error'. */ static void - test_read_hello(int argc UNUSED, char *argv[]) -test_read_hello(const char *type, int expected_error) ++test_read_hello(int argc OVS_UNUSED, char *argv[]) { + const char *type = argv[1]; struct fake_pvconn fpv; struct vconn *vconn; - int fd; + struct stream *stream; fpv_create(type, &fpv); - assert(!vconn_open(fpv.vconn_name, OFP_VERSION, &vconn)); - fd = fpv_accept(&fpv); + CHECK_ERRNO(vconn_open(fpv.vconn_name, OFP_VERSION, &vconn), 0); + vconn_run(vconn); + stream = fpv_accept(&fpv); fpv_destroy(&fpv); - assert(!set_nonblocking(fd)); for (;;) { struct ofp_header hello; int retval; @@@ -310,9 -277,8 +310,9 @@@ test_send_hello(const char *type, cons /* Try connecting and sending a normal hello, which should succeed. */ static void - test_send_plain_hello(int argc UNUSED, char *argv[]) -test_send_plain_hello(const char *type) ++test_send_plain_hello(int argc OVS_UNUSED, char *argv[]) { + const char *type = argv[1]; struct ofp_header hello; hello.version = OFP_VERSION; @@@ -326,9 -292,8 +326,9 @@@ * the specification says that implementations must accept and ignore extra * data). */ static void - test_send_long_hello(int argc UNUSED, char *argv[]) -test_send_long_hello(const char *type) ++test_send_long_hello(int argc OVS_UNUSED, char *argv[]) { + const char *type = argv[1]; struct ofp_header hello; char buffer[sizeof hello * 2]; @@@ -344,9 -309,8 +344,9 @@@ /* Try connecting and sending an echo request instead of a hello, which should * fail with EPROTO. */ static void - test_send_echo_hello(int argc UNUSED, char *argv[]) -test_send_echo_hello(const char *type) ++test_send_echo_hello(int argc OVS_UNUSED, char *argv[]) { + const char *type = argv[1]; struct ofp_header echo; echo.version = OFP_VERSION; @@@ -359,9 -323,8 +359,9 @@@ /* Try connecting and sending a hello packet that has its length field as 0, * which should fail with EPROTO. */ static void - test_send_short_hello(int argc UNUSED, char *argv[]) -test_send_short_hello(const char *type) ++test_send_short_hello(int argc OVS_UNUSED, char *argv[]) { + const char *type = argv[1]; struct ofp_header hello; memset(&hello, 0, sizeof hello); @@@ -371,9 -334,8 +371,9 @@@ /* Try connecting and sending a hello packet that has a bad version, which * should fail with EPROTO. */ static void - test_send_invalid_version_hello(int argc UNUSED, char *argv[]) -test_send_invalid_version_hello(const char *type) ++test_send_invalid_version_hello(int argc OVS_UNUSED, char *argv[]) { + const char *type = argv[1]; struct ofp_header hello; hello.version = OFP_VERSION - 1; @@@ -383,20 -345,8 +383,20 @@@ test_send_hello(type, &hello, sizeof hello, EPROTO); } +static const struct command commands[] = { + {"refuse-connection", 1, 1, test_refuse_connection}, + {"accept-then-close", 1, 1, test_accept_then_close}, + {"read-hello", 1, 1, test_read_hello}, + {"send-plain-hello", 1, 1, test_send_plain_hello}, + {"send-long-hello", 1, 1, test_send_long_hello}, + {"send-echo-hello", 1, 1, test_send_echo_hello}, + {"send-short-hello", 1, 1, test_send_short_hello}, + {"send-invalid-version-hello", 1, 1, test_send_invalid_version_hello}, + {NULL, 0, 0, NULL}, +}; + int - main(int argc, char *argv[]) + main(int argc OVS_UNUSED, char *argv[]) { set_program_name(argv[0]); time_init(); diff --cc utilities/ovs-dpctl.c index ebcf2e23,133dd401..eb78a573 --- a/utilities/ovs-dpctl.c +++ b/utilities/ovs-dpctl.c @@@ -176,30 -212,11 +176,30 @@@ static int if_up(const char *netdev_nam return retval; } +static int +parsed_dpif_open(const char *arg_, bool create, struct dpif **dpifp) +{ + int result; + char *name, *type; + + dp_parse_name(arg_, &name, &type); + + if (create) { + result = dpif_create(name, type, dpifp); + } else { + result = dpif_open(name, type, dpifp); + } + + free(name); + free(type); + return result; +} + static void - do_add_dp(int argc UNUSED, char *argv[]) + do_add_dp(int argc OVS_UNUSED, char *argv[]) { struct dpif *dpif; - run(dpif_create(argv[1], &dpif), "add_dp"); + run(parsed_dpif_open(argv[1], true, &dpif), "add_dp"); dpif_close(dpif); if (argc > 2) { do_add_if(argc, argv); @@@ -207,10 -224,10 +207,10 @@@ } static void - do_del_dp(int argc UNUSED, char *argv[]) + do_del_dp(int argc OVS_UNUSED, char *argv[]) { struct dpif *dpif; - run(dpif_open(argv[1], &dpif), "opening datapath"); + run(parsed_dpif_open(argv[1], false, &dpif), "opening datapath"); run(dpif_delete(dpif), "del_dp"); dpif_close(dpif); } @@@ -418,31 -435,20 +418,31 @@@ do_show(int argc, char *argv[] } static void - do_dump_dps(int argc UNUSED, char *argv[] UNUSED) + do_dump_dps(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) { - struct svec all_dps; + struct svec dpif_names, dpif_types; unsigned int i; - int error; + int error = 0; + + svec_init(&dpif_names); + svec_init(&dpif_types); + dp_enumerate_types(&dpif_types); + + for (i = 0; i < dpif_types.n; i++) { + unsigned int j; + int retval; - svec_init(&all_dps); - error = dp_enumerate(&all_dps); + retval = dp_enumerate_names(dpif_types.names[i], &dpif_names); + if (retval) { + error = retval; + } - for (i = 0; i < all_dps.n; i++) { - struct dpif *dpif; - if (!dpif_open(all_dps.names[i], &dpif)) { - printf("%s\n", dpif_name(dpif)); - dpif_close(dpif); + for (j = 0; j < dpif_names.n; j++) { + struct dpif *dpif; + if (!dpif_open(dpif_names.names[j], dpif_types.names[i], &dpif)) { + printf("%s\n", dpif_name(dpif)); + dpif_close(dpif); + } } } diff --cc utilities/ovs-ofctl.c index 7f24309b,c36a3bc8..2a171533 --- a/utilities/ovs-ofctl.c +++ b/utilities/ovs-ofctl.c @@@ -339,7 -372,7 +339,7 @@@ dump_trivial_stats_transaction(const ch } static void - do_show(int argc UNUSED, char *argv[]) -do_show(const struct settings *s OVS_UNUSED, int argc OVS_UNUSED, char *argv[]) ++do_show(int argc OVS_UNUSED, char *argv[]) { dump_trivial_transaction(argv[1], OFPT_FEATURES_REQUEST); dump_trivial_transaction(argv[1], OFPT_GET_CONFIG_REQUEST); @@@ -378,13 -411,15 +378,13 @@@ do_status(int argc, char *argv[] } static void - do_dump_desc(int argc UNUSED, char *argv[]) -do_dump_desc(const struct settings *s OVS_UNUSED, - int argc OVS_UNUSED, char *argv[]) ++do_dump_desc(int argc OVS_UNUSED, char *argv[]) { dump_trivial_stats_transaction(argv[1], OFPST_DESC); } static void - do_dump_tables(int argc UNUSED, char *argv[]) -do_dump_tables(const struct settings *s OVS_UNUSED, - int argc OVS_UNUSED, char *argv[]) ++do_dump_tables(int argc OVS_UNUSED, char *argv[]) { dump_trivial_stats_transaction(argv[1], OFPST_TABLE); } @@@ -812,7 -847,8 +812,7 @@@ do_dump_aggregate(int argc, char *argv[ } static void - do_add_flow(int argc UNUSED, char *argv[]) -do_add_flow(const struct settings *s OVS_UNUSED, int argc OVS_UNUSED, - char *argv[]) ++do_add_flow(int argc OVS_UNUSED, char *argv[]) { struct vconn *vconn; struct ofpbuf *buffer; @@@ -840,7 -876,8 +840,7 @@@ } static void - do_add_flows(int argc UNUSED, char *argv[]) -do_add_flows(const struct settings *s OVS_UNUSED, int argc OVS_UNUSED, - char *argv[]) ++do_add_flows(int argc OVS_UNUSED, char *argv[]) { struct vconn *vconn; FILE *file; @@@ -893,7 -930,7 +893,7 @@@ } static void - do_mod_flows(int argc UNUSED, char *argv[]) -do_mod_flows(const struct settings *s, int argc OVS_UNUSED, char *argv[]) ++do_mod_flows(int argc OVS_UNUSED, char *argv[]) { uint16_t priority, idle_timeout, hard_timeout; struct vconn *vconn; @@@ -954,7 -991,8 +954,7 @@@ static void do_del_flows(int argc, cha } static void - do_monitor(int argc UNUSED, char *argv[]) -do_monitor(const struct settings *s OVS_UNUSED, - int argc OVS_UNUSED, char *argv[]) ++do_monitor(int argc OVS_UNUSED, char *argv[]) { struct vconn *vconn; @@@ -979,13 -1017,15 +979,13 @@@ } static void - do_dump_ports(int argc UNUSED, char *argv[]) -do_dump_ports(const struct settings *s OVS_UNUSED, - int argc OVS_UNUSED, char *argv[]) ++do_dump_ports(int argc OVS_UNUSED, char *argv[]) { dump_trivial_stats_transaction(argv[1], OFPST_PORT); } static void - do_probe(int argc UNUSED, char *argv[]) -do_probe(const struct settings *s OVS_UNUSED, - int argc OVS_UNUSED, char *argv[]) ++do_probe(int argc OVS_UNUSED, char *argv[]) { struct ofpbuf *request; struct vconn *vconn; @@@ -1002,7 -1042,8 +1002,7 @@@ } static void - do_mod_port(int argc UNUSED, char *argv[]) -do_mod_port(const struct settings *s OVS_UNUSED, - int argc OVS_UNUSED, char *argv[]) ++do_mod_port(int argc OVS_UNUSED, char *argv[]) { struct ofpbuf *request, *reply; struct ofp_switch_features *osf; @@@ -1129,7 -1170,8 +1129,7 @@@ do_ping(int argc, char *argv[] } static void - do_benchmark(int argc UNUSED, char *argv[]) -do_benchmark(const struct settings *s OVS_UNUSED, - int argc OVS_UNUSED, char *argv[]) ++do_benchmark(int argc OVS_UNUSED, char *argv[]) { size_t max_payload = 65535 - sizeof(struct ofp_header); struct timeval start, end; @@@ -1172,7 -1214,73 +1172,7 @@@ } static void - do_help(int argc UNUSED, char *argv[] UNUSED) -do_execute(const struct settings *s OVS_UNUSED, int argc, char *argv[]) -{ - struct vconn *vconn; - struct ofpbuf *request; - struct nicira_header *nicira; - struct nx_command_reply *ncr; - uint32_t xid; - int i; - - nicira = make_openflow(sizeof *nicira, OFPT_VENDOR, &request); - xid = nicira->header.xid; - nicira->vendor = htonl(NX_VENDOR_ID); - nicira->subtype = htonl(NXT_COMMAND_REQUEST); - ofpbuf_put(request, argv[2], strlen(argv[2])); - for (i = 3; i < argc; i++) { - ofpbuf_put_zeros(request, 1); - ofpbuf_put(request, argv[i], strlen(argv[i])); - } - update_openflow_length(request); - - open_vconn(argv[1], &vconn); - run(vconn_send_block(vconn, request), "send"); - - for (;;) { - struct ofpbuf *reply; - uint32_t status; - - run(vconn_recv_xid(vconn, xid, &reply), "recv_xid"); - if (reply->size < sizeof *ncr) { - ovs_fatal(0, "reply is too short (%zu bytes < %zu bytes)", - reply->size, sizeof *ncr); - } - ncr = reply->data; - if (ncr->nxh.header.type != OFPT_VENDOR - || ncr->nxh.vendor != htonl(NX_VENDOR_ID) - || ncr->nxh.subtype != htonl(NXT_COMMAND_REPLY)) { - ovs_fatal(0, "reply is invalid"); - } - - status = ntohl(ncr->status); - if (status & NXT_STATUS_STARTED) { - /* Wait for a second reply. */ - continue; - } else if (status & NXT_STATUS_EXITED) { - fprintf(stderr, "process terminated normally with exit code %d", - status & NXT_STATUS_EXITSTATUS); - } else if (status & NXT_STATUS_SIGNALED) { - fprintf(stderr, "process terminated by signal %d", - status & NXT_STATUS_TERMSIG); - } else if (status & NXT_STATUS_ERROR) { - fprintf(stderr, "error executing command"); - } else { - fprintf(stderr, "process terminated for unknown reason"); - } - if (status & NXT_STATUS_COREDUMP) { - fprintf(stderr, " (core dumped)"); - } - putc('\n', stderr); - - fwrite(ncr + 1, reply->size - sizeof *ncr, 1, stdout); - break; - } -} - -static void -do_help(const struct settings *s OVS_UNUSED, - int argc OVS_UNUSED, char *argv[] OVS_UNUSED) ++do_help(int argc OVS_UNUSED, char *argv[] OVS_UNUSED) { usage(); } diff --cc utilities/ovs-vsctl.c index 498a0b0b,00000000..84db728b mode 100644,000000..100644 --- a/utilities/ovs-vsctl.c +++ b/utilities/ovs-vsctl.c @@@ -1,2519 -1,0 +1,2519 @@@ +/* + * Copyright (c) 2009, 2010 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "command-line.h" +#include "compiler.h" +#include "dirs.h" +#include "dynamic-string.h" +#include "json.h" +#include "ovsdb-data.h" +#include "ovsdb-idl.h" +#include "poll-loop.h" +#include "process.h" +#include "svec.h" +#include "vswitchd/vswitch-idl.h" +#include "timeval.h" +#include "util.h" + +#include "vlog.h" +#define THIS_MODULE VLM_vsctl + +/* vsctl_fatal() also logs the error, so it is preferred in this file. */ +#define ovs_fatal please_use_vsctl_fatal_instead_of_ovs_fatal + +struct vsctl_context; + +typedef void vsctl_handler_func(struct vsctl_context *); + +struct vsctl_command_syntax { + const char *name; + int min_args; + int max_args; + vsctl_handler_func *run; + vsctl_handler_func *postprocess; + const char *options; +}; + +struct vsctl_command { + /* Data that remains constant after initialization. */ + const struct vsctl_command_syntax *syntax; + int argc; + char **argv; + struct shash options; + + /* Data modified by commands. */ + struct ds output; +}; + +/* --db: The database server to contact. */ +static const char *db; + +/* --oneline: Write each command's output as a single line? */ +static bool oneline; + +/* --dry-run: Do not commit any changes. */ +static bool dry_run; + +/* --no-wait: Wait for ovs-vswitchd to reload its configuration? */ +static bool wait_for_reload = true; + +/* --timeout: Time to wait for a connection to 'db'. */ +static int timeout = 5; + +/* All supported commands. */ +static const struct vsctl_command_syntax all_commands[]; + +/* The IDL we're using and the current transaction, if any. + * This is for use by vsctl_exit() only, to allow it to clean up. + * Other code should use its context arguments. */ +static struct ovsdb_idl *the_idl; +static struct ovsdb_idl_txn *the_idl_txn; + +static void vsctl_exit(int status) NO_RETURN; +static void vsctl_fatal(const char *, ...) PRINTF_FORMAT(1, 2) NO_RETURN; +static char *default_db(void); +static void usage(void) NO_RETURN; +static void parse_options(int argc, char *argv[]); + +static struct vsctl_command *parse_commands(int argc, char *argv[], + size_t *n_commandsp); +static void parse_command(int argc, char *argv[], struct vsctl_command *); +static void do_vsctl(const char *args, + struct vsctl_command *, size_t n_commands, + struct ovsdb_idl *); + +int +main(int argc, char *argv[]) +{ + struct ovsdb_idl *idl; + unsigned int seqno; + struct vsctl_command *commands; + size_t n_commands; + char *args; + int trials; + + set_program_name(argv[0]); + signal(SIGPIPE, SIG_IGN); + time_init(); + vlog_init(); + vlog_set_levels(VLM_ANY_MODULE, VLF_CONSOLE, VLL_WARN); + vlog_set_levels(VLM_reconnect, VLF_ANY_FACILITY, VLL_WARN); + ovsrec_init(); + + /* Log our arguments. This is often valuable for debugging systems. */ + args = process_escape_args(argv); + VLOG_INFO("Called as %s", args); + + /* Parse command line. */ + parse_options(argc, argv); + commands = parse_commands(argc - optind, argv + optind, &n_commands); + + if (timeout) { + time_alarm(timeout); + } + + /* Now execute the commands. */ + idl = the_idl = ovsdb_idl_create(db, &ovsrec_idl_class); + seqno = ovsdb_idl_get_seqno(idl); + trials = 0; + for (;;) { + unsigned int new_seqno; + + ovsdb_idl_run(idl); + new_seqno = ovsdb_idl_get_seqno(idl); + if (new_seqno != seqno) { + if (++trials > 5) { + vsctl_fatal("too many database inconsistency failures"); + } + do_vsctl(args, commands, n_commands, idl); + seqno = new_seqno; + } + + ovsdb_idl_wait(idl); + poll_block(); + } +} + +static void +parse_options(int argc, char *argv[]) +{ + enum { + OPT_DB = UCHAR_MAX + 1, + OPT_ONELINE, + OPT_NO_SYSLOG, + OPT_NO_WAIT, + OPT_DRY_RUN, + VLOG_OPTION_ENUMS + }; + static struct option long_options[] = { + {"db", required_argument, 0, OPT_DB}, + {"no-syslog", no_argument, 0, OPT_NO_SYSLOG}, + {"no-wait", no_argument, 0, OPT_NO_WAIT}, + {"dry-run", no_argument, 0, OPT_DRY_RUN}, + {"oneline", no_argument, 0, OPT_ONELINE}, + {"timeout", required_argument, 0, 't'}, + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'V'}, + VLOG_LONG_OPTIONS, + {0, 0, 0, 0}, + }; + + + for (;;) { + int c; + + c = getopt_long(argc, argv, "+v::hVt:", long_options, NULL); + if (c == -1) { + break; + } + + switch (c) { + case OPT_DB: + db = optarg; + break; + + case OPT_ONELINE: + oneline = true; + break; + + case OPT_NO_SYSLOG: + vlog_set_levels(VLM_vsctl, VLF_SYSLOG, VLL_WARN); + break; + + case OPT_NO_WAIT: + wait_for_reload = false; + break; + + case OPT_DRY_RUN: + dry_run = true; + break; + + case 'h': + usage(); + + case 'V': + OVS_PRINT_VERSION(0, 0); + exit(EXIT_SUCCESS); + + case 't': + timeout = strtoul(optarg, NULL, 10); + if (timeout < 0) { + vsctl_fatal("value %s on -t or --timeout is invalid", + optarg); + } + break; + + VLOG_OPTION_HANDLERS + + case '?': + exit(EXIT_FAILURE); + + default: + abort(); + } + } + + if (!db) { + db = default_db(); + } +} + +static struct vsctl_command * +parse_commands(int argc, char *argv[], size_t *n_commandsp) +{ + struct vsctl_command *commands; + size_t n_commands, allocated_commands; + int i, start; + + commands = NULL; + n_commands = allocated_commands = 0; + + for (start = i = 0; i <= argc; i++) { + if (i == argc || !strcmp(argv[i], "--")) { + if (i > start) { + if (n_commands >= allocated_commands) { + struct vsctl_command *c; + + commands = x2nrealloc(commands, &allocated_commands, + sizeof *commands); + for (c = commands; c < &commands[n_commands]; c++) { + shash_moved(&c->options); + } + } + parse_command(i - start, &argv[start], + &commands[n_commands++]); + } + start = i + 1; + } + } + if (!n_commands) { + vsctl_fatal("missing command name (use --help for help)"); + } + *n_commandsp = n_commands; + return commands; +} + +static void +parse_command(int argc, char *argv[], struct vsctl_command *command) +{ + const struct vsctl_command_syntax *p; + int i; + + shash_init(&command->options); + for (i = 0; i < argc; i++) { + if (argv[i][0] != '-') { + break; + } + if (!shash_add_once(&command->options, argv[i], NULL)) { + vsctl_fatal("'%s' option specified multiple times", argv[i]); + } + } + if (i == argc) { + vsctl_fatal("missing command name"); + } + + for (p = all_commands; p->name; p++) { + if (!strcmp(p->name, argv[i])) { + struct shash_node *node; + int n_arg; + + SHASH_FOR_EACH (node, &command->options) { + const char *s = strstr(p->options, node->name); + int end = s ? s[strlen(node->name)] : EOF; + if (end != ',' && end != ' ' && end != '\0') { + vsctl_fatal("'%s' command has no '%s' option", + argv[i], node->name); + } + } + + n_arg = argc - i - 1; + if (n_arg < p->min_args) { + vsctl_fatal("'%s' command requires at least %d arguments", + p->name, p->min_args); + } else if (n_arg > p->max_args) { + int j; + + for (j = i + 1; j < argc; j++) { + if (argv[j][0] == '-') { + vsctl_fatal("'%s' command takes at most %d arguments " + "(note that options must precede command " + "names and follow a \"--\" argument)", + p->name, p->max_args); + } + } + + vsctl_fatal("'%s' command takes at most %d arguments", + p->name, p->max_args); + } else { + command->syntax = p; + command->argc = n_arg + 1; + command->argv = &argv[i]; + return; + } + } + } + + vsctl_fatal("unknown command '%s'; use --help for help", argv[i]); +} + +static void +vsctl_fatal(const char *format, ...) +{ + char *message; + va_list args; + + va_start(args, format); + message = xvasprintf(format, args); + va_end(args); + + vlog_set_levels(VLM_vsctl, VLF_CONSOLE, VLL_EMER); + VLOG_ERR("%s", message); + ovs_error(0, "%s", message); + vsctl_exit(EXIT_FAILURE); +} + +/* Frees the current transaction and the underlying IDL and then calls + * exit(status). + * + * Freeing the transaction and the IDL is not strictly necessary, but it makes + * for a clean memory leak report from valgrind in the normal case. That makes + * it easier to notice real memory leaks. */ +static void +vsctl_exit(int status) +{ + if (the_idl_txn) { + ovsdb_idl_txn_abort(the_idl_txn); + ovsdb_idl_txn_destroy(the_idl_txn); + } + ovsdb_idl_destroy(the_idl); + exit(status); +} + +static void +usage(void) +{ + printf("\ +%s: ovs-vswitchd management utility\n\ +usage: %s [OPTIONS] COMMAND [ARG...]\n\ +\n\ +Bridge commands:\n\ + add-br BRIDGE create a new bridge named BRIDGE\n\ + add-br BRIDGE PARENT VLAN create new fake BRIDGE in PARENT on VLAN\n\ + del-br BRIDGE delete BRIDGE and all of its ports\n\ + list-br print the names of all the bridges\n\ + br-exists BRIDGE test whether BRIDGE exists\n\ + br-to-vlan BRIDGE print the VLAN which BRIDGE is on\n\ + br-to-parent BRIDGE print the parent of BRIDGE\n\ + br-set-external-id BRIDGE KEY VALUE set KEY on BRIDGE to VALUE\n\ + br-set-external-id BRIDGE KEY unset KEY on BRIDGE\n\ + br-get-external-id BRIDGE KEY print value of KEY on BRIDGE\n\ + br-get-external-id BRIDGE list key-value pairs on BRIDGE\n\ +\n\ +Port commands:\n\ + list-ports BRIDGE print the names of all the ports on BRIDGE\n\ + add-port BRIDGE PORT add network device PORT to BRIDGE\n\ + add-bond BRIDGE PORT IFACE... add bonded port PORT in BRIDGE from IFACES\n\ + del-port [BRIDGE] PORT delete PORT (which may be bonded) from BRIDGE\n\ + port-to-br PORT print name of bridge that contains PORT\n\ +A bond is considered to be a single port.\n\ +\n\ +Interface commands (a bond consists of multiple interfaces):\n\ + list-ifaces BRIDGE print the names of all interfaces on BRIDGE\n\ + iface-to-br IFACE print name of bridge that contains IFACE\n\ +\n\ +Controller commands:\n\ + get-controller [BRIDGE] print the controller for BRIDGE\n\ + del-controller [BRIDGE] delete the controller for BRIDGE\n\ + set-controller [BRIDGE] TARGET set the controller for BRIDGE to TARGET\n\ + get-fail-mode [BRIDGE] print the fail-mode for BRIDGE\n\ + del-fail-mode [BRIDGE] delete the fail-mode for BRIDGE\n\ + set-fail-mode [BRIDGE] MODE set the fail-mode for BRIDGE to MODE\n\ +\n\ +SSL commands:\n\ + get-ssl print the SSL configuration\n\ + del-ssl delete the SSL configuration\n\ + set-ssl PRIV-KEY CERT CA-CERT set the SSL configuration\n\ +\n\ +Database commands:\n\ + list TBL [REC] list RECord (or all records) in TBL\n\ + get TBL REC COL[:KEY] print values of COLumns in RECORD in TBL\n\ + set TBL REC COL[:KEY]=VALUE set COLumn values in RECord in TBL\n\ + add TBL REC COL [KEY=]VALUE add (KEY=)VALUE to COLumn in RECord in TBL\n\ + remove TBL REC COL [KEY=]VALUE remove (KEY=)VALUE from COLumn\n\ + clear TBL REC COL clear values from COLumn in RECord in TBL\n\ + create TBL COL[:KEY]=VALUE create and initialize new record\n\ + destroy TBL REC delete REC from TBL\n\ +Potentially unsafe database commands require --force option.\n\ +\n\ +Options:\n\ + --db=DATABASE connect to DATABASE\n\ + (default: %s)\n\ + --oneline print exactly one line of output per command\n", + program_name, program_name, default_db()); + vlog_usage(); + printf("\n\ +Other options:\n\ + -h, --help display this help message\n\ + -V, --version display version information\n"); + exit(EXIT_SUCCESS); +} + +static char * +default_db(void) +{ + static char *def; + if (!def) { + def = xasprintf("unix:%s/ovsdb-server", ovs_rundir); + } + return def; +} + +struct vsctl_context { + /* Read-only. */ + int argc; + char **argv; + struct shash options; + + /* Modifiable state. */ + struct ds output; + struct ovsdb_idl *idl; + struct ovsdb_idl_txn *txn; + const struct ovsrec_open_vswitch *ovs; +}; + +struct vsctl_bridge { + struct ovsrec_bridge *br_cfg; + char *name; + struct ovsrec_controller *ctrl; + struct vsctl_bridge *parent; + int vlan; +}; + +struct vsctl_port { + struct ovsrec_port *port_cfg; + struct vsctl_bridge *bridge; +}; + +struct vsctl_iface { + struct ovsrec_interface *iface_cfg; + struct vsctl_port *port; +}; + +struct vsctl_info { + struct shash bridges; + struct shash ports; + struct shash ifaces; + struct ovsrec_controller *ctrl; +}; + +static char * +vsctl_context_to_string(const struct vsctl_context *ctx) +{ + const struct shash_node *node; + struct svec words; + char *s; + int i; + + svec_init(&words); + SHASH_FOR_EACH (node, &ctx->options) { + svec_add(&words, node->name); + } + for (i = 0; i < ctx->argc; i++) { + svec_add(&words, ctx->argv[i]); + } + svec_terminate(&words); + + s = process_escape_args(words.names); + + svec_destroy(&words); + + return s; +} + +static struct vsctl_bridge * +add_bridge(struct vsctl_info *b, + struct ovsrec_bridge *br_cfg, const char *name, + struct vsctl_bridge *parent, int vlan) +{ + struct vsctl_bridge *br = xmalloc(sizeof *br); + br->br_cfg = br_cfg; + br->name = xstrdup(name); + br->parent = parent; + br->vlan = vlan; + br->ctrl = parent ? parent->br_cfg->controller : br_cfg->controller; + shash_add(&b->bridges, br->name, br); + return br; +} + +static bool +port_is_fake_bridge(const struct ovsrec_port *port_cfg) +{ + return (port_cfg->fake_bridge + && port_cfg->tag + && *port_cfg->tag >= 1 && *port_cfg->tag <= 4095); +} + +static struct vsctl_bridge * +find_vlan_bridge(struct vsctl_info *info, + struct vsctl_bridge *parent, int vlan) +{ + struct shash_node *node; + + SHASH_FOR_EACH (node, &info->bridges) { + struct vsctl_bridge *br = node->data; + if (br->parent == parent && br->vlan == vlan) { + return br; + } + } + + return NULL; +} + +static void +free_info(struct vsctl_info *info) +{ + struct shash_node *node; + + SHASH_FOR_EACH (node, &info->bridges) { + struct vsctl_bridge *bridge = node->data; + free(bridge->name); + free(bridge); + } + shash_destroy(&info->bridges); + + SHASH_FOR_EACH (node, &info->ports) { + struct vsctl_port *port = node->data; + free(port); + } + shash_destroy(&info->ports); + + SHASH_FOR_EACH (node, &info->ifaces) { + struct vsctl_iface *iface = node->data; + free(iface); + } + shash_destroy(&info->ifaces); +} + +static void +get_info(const struct ovsrec_open_vswitch *ovs, struct vsctl_info *info) +{ + struct shash bridges, ports; + size_t i; + + shash_init(&info->bridges); + shash_init(&info->ports); + shash_init(&info->ifaces); + + info->ctrl = ovs->controller; + + shash_init(&bridges); + shash_init(&ports); + for (i = 0; i < ovs->n_bridges; i++) { + struct ovsrec_bridge *br_cfg = ovs->bridges[i]; + struct vsctl_bridge *br; + size_t j; + + if (!shash_add_once(&bridges, br_cfg->name, NULL)) { + VLOG_WARN("%s: database contains duplicate bridge name", + br_cfg->name); + continue; + } + br = add_bridge(info, br_cfg, br_cfg->name, NULL, 0); + if (!br) { + continue; + } + + for (j = 0; j < br_cfg->n_ports; j++) { + struct ovsrec_port *port_cfg = br_cfg->ports[j]; + + if (!shash_add_once(&ports, port_cfg->name, NULL)) { + VLOG_WARN("%s: database contains duplicate port name", + port_cfg->name); + continue; + } + + if (port_is_fake_bridge(port_cfg) + && shash_add_once(&bridges, port_cfg->name, NULL)) { + add_bridge(info, NULL, port_cfg->name, br, *port_cfg->tag); + } + } + } + shash_destroy(&bridges); + shash_destroy(&ports); + + shash_init(&bridges); + shash_init(&ports); + for (i = 0; i < ovs->n_bridges; i++) { + struct ovsrec_bridge *br_cfg = ovs->bridges[i]; + struct vsctl_bridge *br; + size_t j; + + if (!shash_add_once(&bridges, br_cfg->name, NULL)) { + continue; + } + br = shash_find_data(&info->bridges, br_cfg->name); + for (j = 0; j < br_cfg->n_ports; j++) { + struct ovsrec_port *port_cfg = br_cfg->ports[j]; + struct vsctl_port *port; + size_t k; + + if (!shash_add_once(&ports, port_cfg->name, NULL)) { + continue; + } + + if (port_is_fake_bridge(port_cfg) + && !shash_add_once(&bridges, port_cfg->name, NULL)) { + continue; + } + + port = xmalloc(sizeof *port); + port->port_cfg = port_cfg; + if (port_cfg->tag + && *port_cfg->tag >= 1 && *port_cfg->tag <= 4095) { + port->bridge = find_vlan_bridge(info, br, *port_cfg->tag); + if (!port->bridge) { + port->bridge = br; + } + } else { + port->bridge = br; + } + shash_add(&info->ports, port_cfg->name, port); + + for (k = 0; k < port_cfg->n_interfaces; k++) { + struct ovsrec_interface *iface_cfg = port_cfg->interfaces[k]; + struct vsctl_iface *iface; + + if (shash_find(&info->ifaces, iface_cfg->name)) { + VLOG_WARN("%s: database contains duplicate interface name", + iface_cfg->name); + continue; + } + + iface = xmalloc(sizeof *iface); + iface->iface_cfg = iface_cfg; + iface->port = port; + shash_add(&info->ifaces, iface_cfg->name, iface); + } + } + } + shash_destroy(&bridges); + shash_destroy(&ports); +} + +static void +check_conflicts(struct vsctl_info *info, const char *name, + char *msg) +{ + struct vsctl_iface *iface; + struct vsctl_port *port; + + if (shash_find(&info->bridges, name)) { + vsctl_fatal("%s because a bridge named %s already exists", + msg, name); + } + + port = shash_find_data(&info->ports, name); + if (port) { + vsctl_fatal("%s because a port named %s already exists on " + "bridge %s", msg, name, port->bridge->name); + } + + iface = shash_find_data(&info->ifaces, name); + if (iface) { + vsctl_fatal("%s because an interface named %s already exists " + "on bridge %s", msg, name, iface->port->bridge->name); + } + + free(msg); +} + +static struct vsctl_bridge * +find_bridge(struct vsctl_info *info, const char *name, bool must_exist) +{ + struct vsctl_bridge *br = shash_find_data(&info->bridges, name); + if (must_exist && !br) { + vsctl_fatal("no bridge named %s", name); + } + return br; +} + +static struct vsctl_bridge * +find_real_bridge(struct vsctl_info *info, const char *name, bool must_exist) +{ + struct vsctl_bridge *br = find_bridge(info, name, must_exist); + if (br && br->parent) { + vsctl_fatal("%s is a fake bridge", name); + } + return br; +} + +static struct vsctl_port * +find_port(struct vsctl_info *info, const char *name, bool must_exist) +{ + struct vsctl_port *port = shash_find_data(&info->ports, name); + if (port && !strcmp(name, port->bridge->name)) { + port = NULL; + } + if (must_exist && !port) { + vsctl_fatal("no port named %s", name); + } + return port; +} + +static struct vsctl_iface * +find_iface(struct vsctl_info *info, const char *name, bool must_exist) +{ + struct vsctl_iface *iface = shash_find_data(&info->ifaces, name); + if (iface && !strcmp(name, iface->port->bridge->name)) { + iface = NULL; + } + if (must_exist && !iface) { + vsctl_fatal("no interface named %s", name); + } + return iface; +} + +static void +bridge_insert_port(struct ovsrec_bridge *br, struct ovsrec_port *port) +{ + struct ovsrec_port **ports; + size_t i; + + ports = xmalloc(sizeof *br->ports * (br->n_ports + 1)); + for (i = 0; i < br->n_ports; i++) { + ports[i] = br->ports[i]; + } + ports[br->n_ports] = port; + ovsrec_bridge_set_ports(br, ports, br->n_ports + 1); + free(ports); +} + +static void +bridge_delete_port(struct ovsrec_bridge *br, struct ovsrec_port *port) +{ + struct ovsrec_port **ports; + size_t i, n; + + ports = xmalloc(sizeof *br->ports * br->n_ports); + for (i = n = 0; i < br->n_ports; i++) { + if (br->ports[i] != port) { + ports[n++] = br->ports[i]; + } + } + ovsrec_bridge_set_ports(br, ports, n); + free(ports); +} + +static void +ovs_insert_bridge(const struct ovsrec_open_vswitch *ovs, + struct ovsrec_bridge *bridge) +{ + struct ovsrec_bridge **bridges; + size_t i; + + bridges = xmalloc(sizeof *ovs->bridges * (ovs->n_bridges + 1)); + for (i = 0; i < ovs->n_bridges; i++) { + bridges[i] = ovs->bridges[i]; + } + bridges[ovs->n_bridges] = bridge; + ovsrec_open_vswitch_set_bridges(ovs, bridges, ovs->n_bridges + 1); + free(bridges); +} + +static void +ovs_delete_bridge(const struct ovsrec_open_vswitch *ovs, + struct ovsrec_bridge *bridge) +{ + struct ovsrec_bridge **bridges; + size_t i, n; + + bridges = xmalloc(sizeof *ovs->bridges * ovs->n_bridges); + for (i = n = 0; i < ovs->n_bridges; i++) { + if (ovs->bridges[i] != bridge) { + bridges[n++] = ovs->bridges[i]; + } + } + ovsrec_open_vswitch_set_bridges(ovs, bridges, n); + free(bridges); +} + +static void - cmd_init(struct vsctl_context *ctx UNUSED) ++cmd_init(struct vsctl_context *ctx OVS_UNUSED) +{ +} + +static void +cmd_add_br(struct vsctl_context *ctx) +{ + bool may_exist = shash_find(&ctx->options, "--may-exist") != 0; + const char *br_name, *parent_name; + struct vsctl_info info; + int vlan; + + br_name = ctx->argv[1]; + if (ctx->argc == 2) { + parent_name = NULL; + vlan = 0; + } else if (ctx->argc == 4) { + parent_name = ctx->argv[2]; + vlan = atoi(ctx->argv[3]); + if (vlan < 1 || vlan > 4095) { + vsctl_fatal("%s: vlan must be between 1 and 4095", ctx->argv[0]); + } + } else { + vsctl_fatal("'%s' command takes exactly 1 or 3 arguments", + ctx->argv[0]); + } + + get_info(ctx->ovs, &info); + if (may_exist) { + struct vsctl_bridge *br; + + br = find_bridge(&info, br_name, false); + if (br) { + if (!parent_name) { + if (br->parent) { + vsctl_fatal("\"--may-exist add-br %s\" but %s is " + "a VLAN bridge for VLAN %d", + br_name, br_name, br->vlan); + } + } else { + if (!br->parent) { + vsctl_fatal("\"--may-exist add-br %s %s %d\" but %s " + "is not a VLAN bridge", + br_name, parent_name, vlan, br_name); + } else if (strcmp(br->parent->name, parent_name)) { + vsctl_fatal("\"--may-exist add-br %s %s %d\" but %s " + "has the wrong parent %s", + br_name, parent_name, vlan, + br_name, br->parent->name); + } else if (br->vlan != vlan) { + vsctl_fatal("\"--may-exist add-br %s %s %d\" but %s " + "is a VLAN bridge for the wrong VLAN %d", + br_name, parent_name, vlan, br_name, br->vlan); + } + } + return; + } + } + check_conflicts(&info, br_name, + xasprintf("cannot create a bridge named %s", br_name)); + + if (!parent_name) { + struct ovsrec_port *port; + struct ovsrec_interface *iface; + struct ovsrec_bridge *br; + + iface = ovsrec_interface_insert(ctx->txn); + ovsrec_interface_set_name(iface, br_name); + + port = ovsrec_port_insert(ctx->txn); + ovsrec_port_set_name(port, br_name); + ovsrec_port_set_interfaces(port, &iface, 1); + + br = ovsrec_bridge_insert(ctx->txn); + ovsrec_bridge_set_name(br, br_name); + ovsrec_bridge_set_ports(br, &port, 1); + + ovs_insert_bridge(ctx->ovs, br); + } else { + struct vsctl_bridge *parent; + struct ovsrec_port *port; + struct ovsrec_interface *iface; + struct ovsrec_bridge *br; + int64_t tag = vlan; + + parent = find_bridge(&info, parent_name, false); + if (parent && parent->vlan) { + vsctl_fatal("cannot create bridge with fake bridge as parent"); + } + if (!parent) { + vsctl_fatal("parent bridge %s does not exist", parent_name); + } + br = parent->br_cfg; + + iface = ovsrec_interface_insert(ctx->txn); + ovsrec_interface_set_name(iface, br_name); + ovsrec_interface_set_type(iface, "internal"); + + port = ovsrec_port_insert(ctx->txn); + ovsrec_port_set_name(port, br_name); + ovsrec_port_set_interfaces(port, &iface, 1); + ovsrec_port_set_fake_bridge(port, true); + ovsrec_port_set_tag(port, &tag, 1); + + bridge_insert_port(br, port); + } + + free_info(&info); +} + +static void +del_port(struct vsctl_info *info, struct vsctl_port *port) +{ + struct shash_node *node; + + SHASH_FOR_EACH (node, &info->ifaces) { + struct vsctl_iface *iface = node->data; + if (iface->port == port) { + ovsrec_interface_delete(iface->iface_cfg); + } + } + ovsrec_port_delete(port->port_cfg); + + bridge_delete_port((port->bridge->parent + ? port->bridge->parent->br_cfg + : port->bridge->br_cfg), port->port_cfg); +} + +static void +cmd_del_br(struct vsctl_context *ctx) +{ + bool must_exist = !shash_find(&ctx->options, "--if-exists"); + struct vsctl_bridge *bridge; + struct vsctl_info info; + + get_info(ctx->ovs, &info); + bridge = find_bridge(&info, ctx->argv[1], must_exist); + if (bridge) { + struct shash_node *node; + + SHASH_FOR_EACH (node, &info.ports) { + struct vsctl_port *port = node->data; + if (port->bridge == bridge || port->bridge->parent == bridge + || !strcmp(port->port_cfg->name, bridge->name)) { + del_port(&info, port); + } + } + if (bridge->br_cfg) { + ovsrec_bridge_delete(bridge->br_cfg); + ovs_delete_bridge(ctx->ovs, bridge->br_cfg); + } + } + free_info(&info); +} + +static void +output_sorted(struct svec *svec, struct ds *output) +{ + const char *name; + size_t i; + + svec_sort(svec); + SVEC_FOR_EACH (i, name, svec) { + ds_put_format(output, "%s\n", name); + } +} + +static void +cmd_list_br(struct vsctl_context *ctx) +{ + struct shash_node *node; + struct vsctl_info info; + struct svec bridges; + + get_info(ctx->ovs, &info); + + svec_init(&bridges); + SHASH_FOR_EACH (node, &info.bridges) { + struct vsctl_bridge *br = node->data; + svec_add(&bridges, br->name); + } + output_sorted(&bridges, &ctx->output); + svec_destroy(&bridges); + + free_info(&info); +} + +static void +cmd_br_exists(struct vsctl_context *ctx) +{ + struct vsctl_info info; + + get_info(ctx->ovs, &info); + if (!find_bridge(&info, ctx->argv[1], false)) { + vsctl_exit(2); + } + free_info(&info); +} + +/* Returns true if 'b_prefix' (of length 'b_prefix_len') concatenated with 'b' + * equals 'a', false otherwise. */ +static bool +key_matches(const char *a, + const char *b_prefix, size_t b_prefix_len, const char *b) +{ + return !strncmp(a, b_prefix, b_prefix_len) && !strcmp(a + b_prefix_len, b); +} + +static void +set_external_id(char **old_keys, char **old_values, size_t old_n, + char *key, char *value, + char ***new_keysp, char ***new_valuesp, size_t *new_np) +{ + char **new_keys; + char **new_values; + size_t new_n; + size_t i; + + new_keys = xmalloc(sizeof *new_keys * (old_n + 1)); + new_values = xmalloc(sizeof *new_values * (old_n + 1)); + new_n = 0; + for (i = 0; i < old_n; i++) { + if (strcmp(key, old_keys[i])) { + new_keys[new_n] = old_keys[i]; + new_values[new_n] = old_values[i]; + new_n++; + } + } + if (value) { + new_keys[new_n] = key; + new_values[new_n] = value; + new_n++; + } + *new_keysp = new_keys; + *new_valuesp = new_values; + *new_np = new_n; +} + +static void +cmd_br_set_external_id(struct vsctl_context *ctx) +{ + struct vsctl_info info; + struct vsctl_bridge *bridge; + char **keys, **values; + size_t n; + + get_info(ctx->ovs, &info); + bridge = find_bridge(&info, ctx->argv[1], true); + if (bridge->br_cfg) { + set_external_id(bridge->br_cfg->key_external_ids, + bridge->br_cfg->value_external_ids, + bridge->br_cfg->n_external_ids, + ctx->argv[2], ctx->argc >= 4 ? ctx->argv[3] : NULL, + &keys, &values, &n); + ovsrec_bridge_set_external_ids(bridge->br_cfg, keys, values, n); + } else { + char *key = xasprintf("fake-bridge-%s", ctx->argv[2]); + struct vsctl_port *port = shash_find_data(&info.ports, ctx->argv[1]); + set_external_id(port->port_cfg->key_external_ids, + port->port_cfg->value_external_ids, + port->port_cfg->n_external_ids, + key, ctx->argc >= 4 ? ctx->argv[3] : NULL, + &keys, &values, &n); + ovsrec_port_set_external_ids(port->port_cfg, keys, values, n); + free(key); + } + free(keys); + free(values); + + free_info(&info); +} + +static void +get_external_id(char **keys, char **values, size_t n, + const char *prefix, const char *key, + struct ds *output) +{ + size_t prefix_len = strlen(prefix); + struct svec svec; + size_t i; + + svec_init(&svec); + for (i = 0; i < n; i++) { + if (!key && !strncmp(keys[i], prefix, prefix_len)) { + svec_add_nocopy(&svec, xasprintf("%s=%s", + keys[i] + prefix_len, values[i])); + } else if (key_matches(keys[i], prefix, prefix_len, key)) { + svec_add(&svec, values[i]); + break; + } + } + output_sorted(&svec, output); + svec_destroy(&svec); +} + +static void +cmd_br_get_external_id(struct vsctl_context *ctx) +{ + struct vsctl_info info; + struct vsctl_bridge *bridge; + + get_info(ctx->ovs, &info); + bridge = find_bridge(&info, ctx->argv[1], true); + if (bridge->br_cfg) { + get_external_id(bridge->br_cfg->key_external_ids, + bridge->br_cfg->value_external_ids, + bridge->br_cfg->n_external_ids, + "", ctx->argc >= 3 ? ctx->argv[2] : NULL, + &ctx->output); + } else { + struct vsctl_port *port = shash_find_data(&info.ports, ctx->argv[1]); + get_external_id(port->port_cfg->key_external_ids, + port->port_cfg->value_external_ids, + port->port_cfg->n_external_ids, + "fake-bridge-", ctx->argc >= 3 ? ctx->argv[2] : NULL, &ctx->output); + } + free_info(&info); +} + + +static void +cmd_list_ports(struct vsctl_context *ctx) +{ + struct vsctl_bridge *br; + struct shash_node *node; + struct vsctl_info info; + struct svec ports; + + get_info(ctx->ovs, &info); + br = find_bridge(&info, ctx->argv[1], true); + + svec_init(&ports); + SHASH_FOR_EACH (node, &info.ports) { + struct vsctl_port *port = node->data; + + if (strcmp(port->port_cfg->name, br->name) && br == port->bridge) { + svec_add(&ports, port->port_cfg->name); + } + } + output_sorted(&ports, &ctx->output); + svec_destroy(&ports); + + free_info(&info); +} + +static void +add_port(struct vsctl_context *ctx, + const char *br_name, const char *port_name, + bool may_exist, bool fake_iface, + char *iface_names[], int n_ifaces) +{ + struct vsctl_info info; + struct vsctl_bridge *bridge; + struct ovsrec_interface **ifaces; + struct ovsrec_port *port; + size_t i; + + get_info(ctx->ovs, &info); + if (may_exist) { + struct vsctl_port *port; + + port = find_port(&info, port_name, false); + if (port) { + struct svec want_names, have_names; + size_t i; + + svec_init(&want_names); + for (i = 0; i < n_ifaces; i++) { + svec_add(&want_names, iface_names[i]); + } + svec_sort(&want_names); + + svec_init(&have_names); + for (i = 0; i < port->port_cfg->n_interfaces; i++) { + svec_add(&have_names, port->port_cfg->interfaces[i]->name); + } + svec_sort(&have_names); + + if (strcmp(port->bridge->name, br_name)) { + char *command = vsctl_context_to_string(ctx); + vsctl_fatal("\"%s\" but %s is actually attached to bridge %s", + command, port_name, port->bridge->name); + } + + if (!svec_equal(&want_names, &have_names)) { + char *have_names_string = svec_join(&have_names, ", ", ""); + char *command = vsctl_context_to_string(ctx); + + vsctl_fatal("\"%s\" but %s actually has interface(s) %s", + command, port_name, have_names_string); + } + + svec_destroy(&want_names); + svec_destroy(&have_names); + + return; + } + } + check_conflicts(&info, port_name, + xasprintf("cannot create a port named %s", port_name)); + for (i = 0; i < n_ifaces; i++) { + check_conflicts(&info, iface_names[i], + xasprintf("cannot create an interface named %s", + iface_names[i])); + } + bridge = find_bridge(&info, br_name, true); + + ifaces = xmalloc(n_ifaces * sizeof *ifaces); + for (i = 0; i < n_ifaces; i++) { + ifaces[i] = ovsrec_interface_insert(ctx->txn); + ovsrec_interface_set_name(ifaces[i], iface_names[i]); + } + + port = ovsrec_port_insert(ctx->txn); + ovsrec_port_set_name(port, port_name); + ovsrec_port_set_interfaces(port, ifaces, n_ifaces); + ovsrec_port_set_bond_fake_iface(port, fake_iface); + free(ifaces); + + if (bridge->vlan) { + int64_t tag = bridge->vlan; + ovsrec_port_set_tag(port, &tag, 1); + } + + bridge_insert_port((bridge->parent ? bridge->parent->br_cfg + : bridge->br_cfg), port); + + free_info(&info); +} + +static void +cmd_add_port(struct vsctl_context *ctx) +{ + bool may_exist = shash_find(&ctx->options, "--may-exist") != 0; + + add_port(ctx, ctx->argv[1], ctx->argv[2], may_exist, false, + &ctx->argv[2], 1); +} + +static void +cmd_add_bond(struct vsctl_context *ctx) +{ + bool may_exist = shash_find(&ctx->options, "--may-exist") != 0; + bool fake_iface = shash_find(&ctx->options, "--fake-iface"); + + add_port(ctx, ctx->argv[1], ctx->argv[2], may_exist, fake_iface, + &ctx->argv[3], ctx->argc - 3); +} + +static void +cmd_del_port(struct vsctl_context *ctx) +{ + bool must_exist = !shash_find(&ctx->options, "--if-exists"); + struct vsctl_info info; + + get_info(ctx->ovs, &info); + if (ctx->argc == 2) { + struct vsctl_port *port = find_port(&info, ctx->argv[1], must_exist); + if (port) { + del_port(&info, port); + } + } else if (ctx->argc == 3) { + struct vsctl_bridge *bridge = find_bridge(&info, ctx->argv[1], true); + struct vsctl_port *port = find_port(&info, ctx->argv[2], must_exist); + + if (port) { + if (port->bridge == bridge) { + del_port(&info, port); + } else if (port->bridge->parent == bridge) { + vsctl_fatal("bridge %s does not have a port %s (although its " + "parent bridge %s does)", + ctx->argv[1], ctx->argv[2], bridge->parent->name); + } else { + vsctl_fatal("bridge %s does not have a port %s", + ctx->argv[1], ctx->argv[2]); + } + } + } + free_info(&info); +} + +static void +cmd_port_to_br(struct vsctl_context *ctx) +{ + struct vsctl_port *port; + struct vsctl_info info; + + get_info(ctx->ovs, &info); + port = find_port(&info, ctx->argv[1], true); + ds_put_format(&ctx->output, "%s\n", port->bridge->name); + free_info(&info); +} + +static void +cmd_br_to_vlan(struct vsctl_context *ctx) +{ + struct vsctl_bridge *bridge; + struct vsctl_info info; + + get_info(ctx->ovs, &info); + bridge = find_bridge(&info, ctx->argv[1], true); + ds_put_format(&ctx->output, "%d\n", bridge->vlan); + free_info(&info); +} + +static void +cmd_br_to_parent(struct vsctl_context *ctx) +{ + struct vsctl_bridge *bridge; + struct vsctl_info info; + + get_info(ctx->ovs, &info); + bridge = find_bridge(&info, ctx->argv[1], true); + if (bridge->parent) { + bridge = bridge->parent; + } + ds_put_format(&ctx->output, "%s\n", bridge->name); + free_info(&info); +} + +static void +cmd_list_ifaces(struct vsctl_context *ctx) +{ + struct vsctl_bridge *br; + struct shash_node *node; + struct vsctl_info info; + struct svec ifaces; + + get_info(ctx->ovs, &info); + br = find_bridge(&info, ctx->argv[1], true); + + svec_init(&ifaces); + SHASH_FOR_EACH (node, &info.ifaces) { + struct vsctl_iface *iface = node->data; + + if (strcmp(iface->iface_cfg->name, br->name) + && br == iface->port->bridge) { + svec_add(&ifaces, iface->iface_cfg->name); + } + } + output_sorted(&ifaces, &ctx->output); + svec_destroy(&ifaces); + + free_info(&info); +} + +static void +cmd_iface_to_br(struct vsctl_context *ctx) +{ + struct vsctl_iface *iface; + struct vsctl_info info; + + get_info(ctx->ovs, &info); + iface = find_iface(&info, ctx->argv[1], true); + ds_put_format(&ctx->output, "%s\n", iface->port->bridge->name); + free_info(&info); +} + +static void +cmd_get_controller(struct vsctl_context *ctx) +{ + struct vsctl_info info; + + get_info(ctx->ovs, &info); + + if (ctx->argc == 1) { + /* Return the controller from the "Open_vSwitch" table */ + if (info.ctrl) { + ds_put_format(&ctx->output, "%s\n", info.ctrl->target); + } + } else { + /* Return the controller for a particular bridge. */ + struct vsctl_bridge *br = find_bridge(&info, ctx->argv[1], true); + + /* If no controller is explicitly defined for the requested + * bridge, fallback to the "Open_vSwitch" table's controller. */ + if (br->ctrl) { + ds_put_format(&ctx->output, "%s\n", br->ctrl->target); + } else if (info.ctrl) { + ds_put_format(&ctx->output, "%s\n", info.ctrl->target); + } + } + + free_info(&info); +} + +static void +cmd_del_controller(struct vsctl_context *ctx) +{ + struct vsctl_info info; + + get_info(ctx->ovs, &info); + + if (ctx->argc == 1) { + if (info.ctrl) { + ovsrec_controller_delete(info.ctrl); + ovsrec_open_vswitch_set_controller(ctx->ovs, NULL); + } + } else { + struct vsctl_bridge *br = find_real_bridge(&info, ctx->argv[1], true); + + if (br->ctrl) { + ovsrec_controller_delete(br->ctrl); + ovsrec_bridge_set_controller(br->br_cfg, NULL); + } + } + + free_info(&info); +} + +static void +cmd_set_controller(struct vsctl_context *ctx) +{ + struct vsctl_info info; + struct ovsrec_controller *ctrl; + + get_info(ctx->ovs, &info); + + if (ctx->argc == 2) { + /* Set the controller in the "Open_vSwitch" table. */ + if (info.ctrl) { + ovsrec_controller_delete(info.ctrl); + } + ctrl = ovsrec_controller_insert(ctx->txn); + ovsrec_controller_set_target(ctrl, ctx->argv[1]); + ovsrec_open_vswitch_set_controller(ctx->ovs, ctrl); + } else { + /* Set the controller for a particular bridge. */ + struct vsctl_bridge *br = find_real_bridge(&info, ctx->argv[1], true); + + if (br->ctrl) { + ovsrec_controller_delete(br->ctrl); + } + ctrl = ovsrec_controller_insert(ctx->txn); + ovsrec_controller_set_target(ctrl, ctx->argv[2]); + ovsrec_bridge_set_controller(br->br_cfg, ctrl); + } + + free_info(&info); +} + +static void +cmd_get_fail_mode(struct vsctl_context *ctx) +{ + struct vsctl_info info; + const char *fail_mode = NULL; + + get_info(ctx->ovs, &info); + + if (ctx->argc == 1) { + /* Return the fail-mode from the "Open_vSwitch" table */ + if (info.ctrl && info.ctrl->fail_mode) { + fail_mode = info.ctrl->fail_mode; + } + } else { + /* Return the fail-mode for a particular bridge. */ + struct vsctl_bridge *br = find_bridge(&info, ctx->argv[1], true); + + /* If no controller or fail-mode is explicitly defined for the + * requested bridge, fallback to the "Open_vSwitch" table's + * setting. */ + if (br->ctrl && br->ctrl->fail_mode) { + fail_mode = br->ctrl->fail_mode; + } else if (info.ctrl && info.ctrl->fail_mode) { + fail_mode = info.ctrl->fail_mode; + } + } + + if (fail_mode && strlen(fail_mode)) { + ds_put_format(&ctx->output, "%s\n", fail_mode); + } + + free_info(&info); +} + +static void +cmd_del_fail_mode(struct vsctl_context *ctx) +{ + struct vsctl_info info; + + get_info(ctx->ovs, &info); + + if (ctx->argc == 1) { + if (info.ctrl && info.ctrl->fail_mode) { + ovsrec_controller_set_fail_mode(info.ctrl, NULL); + } + } else { + struct vsctl_bridge *br = find_real_bridge(&info, ctx->argv[1], true); + + if (br->ctrl && br->ctrl->fail_mode) { + ovsrec_controller_set_fail_mode(br->ctrl, NULL); + } + } + + free_info(&info); +} + +static void +cmd_set_fail_mode(struct vsctl_context *ctx) +{ + struct vsctl_info info; + const char *fail_mode; + + get_info(ctx->ovs, &info); + + fail_mode = (ctx->argc == 2) ? ctx->argv[1] : ctx->argv[2]; + + if (strcmp(fail_mode, "standalone") && strcmp(fail_mode, "secure")) { + vsctl_fatal("fail-mode must be \"standalone\" or \"secure\""); + } + + if (ctx->argc == 2) { + /* Set the fail-mode in the "Open_vSwitch" table. */ + if (!info.ctrl) { + vsctl_fatal("no controller declared"); + } + ovsrec_controller_set_fail_mode(info.ctrl, fail_mode); + } else { + struct vsctl_bridge *br = find_real_bridge(&info, ctx->argv[1], true); + + if (!br->ctrl) { + vsctl_fatal("no controller declared for %s", br->name); + } + ovsrec_controller_set_fail_mode(br->ctrl, fail_mode); + } + + free_info(&info); +} + +static void +cmd_get_ssl(struct vsctl_context *ctx) +{ + struct ovsrec_ssl *ssl = ctx->ovs->ssl; + + if (ssl) { + ds_put_format(&ctx->output, "Private key: %s\n", ssl->private_key); + ds_put_format(&ctx->output, "Certificate: %s\n", ssl->certificate); + ds_put_format(&ctx->output, "CA Certificate: %s\n", ssl->ca_cert); + ds_put_format(&ctx->output, "Bootstrap: %s\n", + ssl->bootstrap_ca_cert ? "true" : "false"); + } +} + +static void +cmd_del_ssl(struct vsctl_context *ctx) +{ + struct ovsrec_ssl *ssl = ctx->ovs->ssl; + + if (ssl) { + ovsrec_ssl_delete(ssl); + ovsrec_open_vswitch_set_ssl(ctx->ovs, NULL); + } +} + +static void +cmd_set_ssl(struct vsctl_context *ctx) +{ + bool bootstrap = shash_find(&ctx->options, "--bootstrap"); + struct ovsrec_ssl *ssl = ctx->ovs->ssl; + + if (ssl) { + ovsrec_ssl_delete(ssl); + } + ssl = ovsrec_ssl_insert(ctx->txn); + + ovsrec_ssl_set_private_key(ssl, ctx->argv[1]); + ovsrec_ssl_set_certificate(ssl, ctx->argv[2]); + ovsrec_ssl_set_ca_cert(ssl, ctx->argv[3]); + + ovsrec_ssl_set_bootstrap_ca_cert(ssl, bootstrap); + + ovsrec_open_vswitch_set_ssl(ctx->ovs, ssl); +} + +/* Parameter commands. */ + +struct vsctl_row_id { + const struct ovsdb_idl_table_class *table; + const struct ovsdb_idl_column *name_column; + const struct ovsdb_idl_column *uuid_column; +}; + +struct vsctl_table_class { + struct ovsdb_idl_table_class *class; + struct vsctl_row_id row_ids[2]; +}; + +static const struct vsctl_table_class tables[] = { + {&ovsrec_table_bridge, + {{&ovsrec_table_bridge, &ovsrec_bridge_col_name, NULL}, + {NULL, NULL, NULL}}}, + + {&ovsrec_table_controller, + {{&ovsrec_table_bridge, + &ovsrec_bridge_col_name, + &ovsrec_bridge_col_controller}, + {&ovsrec_table_open_vswitch, + NULL, + &ovsrec_open_vswitch_col_controller}}}, + + {&ovsrec_table_interface, + {{&ovsrec_table_interface, &ovsrec_interface_col_name, NULL}, + {NULL, NULL, NULL}}}, + + {&ovsrec_table_mirror, + {{&ovsrec_table_mirror, &ovsrec_mirror_col_name, NULL}, + {NULL, NULL, NULL}}}, + + {&ovsrec_table_netflow, + {{&ovsrec_table_bridge, + &ovsrec_bridge_col_name, + &ovsrec_bridge_col_netflow}, + {NULL, NULL, NULL}}}, + + {&ovsrec_table_open_vswitch, + {{&ovsrec_table_open_vswitch, NULL, NULL}, + {NULL, NULL, NULL}}}, + + {&ovsrec_table_port, + {{&ovsrec_table_port, &ovsrec_port_col_name, NULL}, + {NULL, NULL, NULL}}}, + + {&ovsrec_table_ssl, + {{&ovsrec_table_open_vswitch, NULL, &ovsrec_open_vswitch_col_ssl}}}, + + {NULL, {{NULL, NULL, NULL}, {NULL, NULL, NULL}}} +}; + +static void +die_if_error(char *error) +{ + if (error) { + vsctl_fatal("%s", error); + } +} + +static int +to_lower_and_underscores(unsigned c) +{ + return c == '-' ? '_' : tolower(c); +} + +static unsigned int +score_partial_match(const char *name, const char *s) +{ + int score; + + if (!strcmp(name, s)) { + return UINT_MAX; + } + for (score = 0; ; score++, name++, s++) { + if (to_lower_and_underscores(*name) != to_lower_and_underscores(*s)) { + break; + } else if (*name == '\0') { + return UINT_MAX - 1; + } + } + return *s == '\0' ? score : 0; +} + +static const struct vsctl_table_class * +get_table(const char *table_name) +{ + const struct vsctl_table_class *table; + const struct vsctl_table_class *best_match = NULL; + unsigned int best_score = 0; + + for (table = tables; table->class; table++) { + unsigned int score = score_partial_match(table->class->name, + table_name); + if (score > best_score) { + best_match = table; + best_score = score; + } else if (score == best_score) { + best_match = NULL; + } + } + if (best_match) { + return best_match; + } else if (best_score) { + vsctl_fatal("multiple table names match \"%s\"", table_name); + } else { + vsctl_fatal("unknown table \"%s\"", table_name); + } +} + +static const struct ovsdb_idl_row * +get_row_by_id(struct vsctl_context *ctx, const struct vsctl_table_class *table, + const struct vsctl_row_id *id, const char *record_id) +{ + const struct ovsdb_idl_row *referrer, *final; + + if (!id->table) { + return NULL; + } + + if (!id->name_column) { + if (strcmp(record_id, ".")) { + return NULL; + } + referrer = ovsdb_idl_first_row(ctx->idl, id->table); + if (!referrer || ovsdb_idl_next_row(referrer)) { + return NULL; + } + } else { + const struct ovsdb_idl_row *row; + unsigned int best_score = 0; + + /* It might make sense to relax this assertion. */ + assert(id->name_column->type.key.type == OVSDB_TYPE_STRING); + + referrer = NULL; + for (row = ovsdb_idl_first_row(ctx->idl, id->table); + row != NULL && best_score != UINT_MAX; + row = ovsdb_idl_next_row(row)) + { + struct ovsdb_datum name; + + ovsdb_idl_txn_read(row, id->name_column, &name); + if (name.n == 1) { + unsigned int score = score_partial_match(name.keys[0].string, + record_id); + if (score > best_score) { + referrer = row; + best_score = score; + } else if (score == best_score) { + referrer = NULL; + } + } + ovsdb_datum_destroy(&name, &id->name_column->type); + } + if (best_score && !referrer) { + vsctl_fatal("multiple rows in %s match \"%s\"", + table->class->name, record_id); + } + } + if (!referrer) { + return NULL; + } + + final = NULL; + if (id->uuid_column) { + struct ovsdb_datum uuid; + + assert(id->uuid_column->type.key.type == OVSDB_TYPE_UUID); + assert(id->uuid_column->type.value.type == OVSDB_TYPE_VOID); + + ovsdb_idl_txn_read(referrer, id->uuid_column, &uuid); + if (uuid.n == 1) { + final = ovsdb_idl_get_row_for_uuid(ctx->idl, table->class, + &uuid.keys[0].uuid); + } + ovsdb_datum_destroy(&uuid, &id->uuid_column->type); + } else { + final = referrer; + } + + return final; +} + +static const struct ovsdb_idl_row * +get_row(struct vsctl_context *ctx, + const struct vsctl_table_class *table, const char *record_id) +{ + const struct ovsdb_idl_row *row; + struct uuid uuid; + + if (uuid_from_string(&uuid, record_id)) { + row = ovsdb_idl_get_row_for_uuid(ctx->idl, table->class, &uuid); + } else { + int i; + + for (i = 0; i < ARRAY_SIZE(table->row_ids); i++) { + row = get_row_by_id(ctx, table, &table->row_ids[i], record_id); + if (row) { + break; + } + } + } + return row; +} + +static const struct ovsdb_idl_row * +must_get_row(struct vsctl_context *ctx, + const struct vsctl_table_class *table, const char *record_id) +{ + const struct ovsdb_idl_row *row = get_row(ctx, table, record_id); + if (!row) { + vsctl_fatal("no row \"%s\" in table %s", + record_id, table->class->name); + } + return row; +} + +static char * +get_column(const struct vsctl_table_class *table, const char *column_name, + const struct ovsdb_idl_column **columnp) +{ + const struct ovsdb_idl_column *best_match = NULL; + unsigned int best_score = 0; + size_t i; + + for (i = 0; i < table->class->n_columns; i++) { + const struct ovsdb_idl_column *column = &table->class->columns[i]; + unsigned int score = score_partial_match(column->name, column_name); + if (score > best_score) { + best_match = column; + best_score = score; + } else if (score == best_score) { + best_match = NULL; + } + } + + *columnp = best_match; + if (best_match) { + return NULL; + } else if (best_score) { + return xasprintf("%s contains more than one column whose name " + "matches \"%s\"", table->class->name, column_name); + } else { + return xasprintf("%s does not contain a column whose name matches " + "\"%s\"", table->class->name, column_name); + } +} + +static char * WARN_UNUSED_RESULT +parse_column_key_value(const char *arg, const struct vsctl_table_class *table, + const struct ovsdb_idl_column **columnp, + char **keyp, char **valuep) +{ + const char *p = arg; + char *error; + + assert(columnp || keyp); + if (keyp) { + *keyp = NULL; + } + if (valuep) { + *valuep = NULL; + } + + /* Parse column name. */ + if (columnp) { + char *column_name; + + error = ovsdb_token_parse(&p, &column_name); + if (error) { + goto error; + } + if (column_name[0] == '\0') { + free(column_name); + error = xasprintf("%s: missing column name", arg); + goto error; + } + error = get_column(table, column_name, columnp); + free(column_name); + if (error) { + goto error; + } + } + + /* Parse key string. */ + if (*p == ':' || !columnp) { + if (columnp) { + p++; + } else if (!keyp) { + error = xasprintf("%s: key not accepted here", arg); + goto error; + } + error = ovsdb_token_parse(&p, keyp); + if (error) { + goto error; + } + } else if (keyp) { + *keyp = NULL; + } + + /* Parse value string. */ + if (*p == '=') { + if (!valuep) { + error = xasprintf("%s: value not accepted here", arg); + goto error; + } + *valuep = xstrdup(p + 1); + } else { + if (valuep) { + *valuep = NULL; + } + if (*p != '\0') { + error = xasprintf("%s: trailing garbage \"%s\" in argument", + arg, p); + goto error; + } + } + return NULL; + +error: + if (columnp) { + *columnp = NULL; + } + if (keyp) { + free(*keyp); + *keyp = NULL; + } + if (valuep) { + free(*valuep); + *valuep = NULL; + } + return error; +} + +static void +cmd_get(struct vsctl_context *ctx) +{ + bool if_exists = shash_find(&ctx->options, "--if-exists"); + const char *table_name = ctx->argv[1]; + const char *record_id = ctx->argv[2]; + const struct vsctl_table_class *table; + const struct ovsdb_idl_row *row; + struct ds *out = &ctx->output; + int i; + + table = get_table(table_name); + row = must_get_row(ctx, table, record_id); + for (i = 3; i < ctx->argc; i++) { + const struct ovsdb_idl_column *column; + struct ovsdb_datum datum; + char *key_string; + + die_if_error(parse_column_key_value(ctx->argv[i], table, + &column, &key_string, NULL)); + + ovsdb_idl_txn_read(row, column, &datum); + if (key_string) { + union ovsdb_atom key; + unsigned int idx; + + if (column->type.value.type == OVSDB_TYPE_VOID) { + vsctl_fatal("cannot specify key to get for non-map column %s", + column->name); + } + + die_if_error(ovsdb_atom_from_string(&key, + &column->type.key, + key_string)); + + idx = ovsdb_datum_find_key(&datum, &key, + column->type.key.type); + if (idx == UINT_MAX) { + if (!if_exists) { + vsctl_fatal("no key \"%s\" in %s record \"%s\" column %s", + key_string, table->class->name, record_id, + column->name); + } + } else { + ovsdb_atom_to_string(&datum.values[idx], + column->type.value.type, out); + } + ovsdb_atom_destroy(&key, column->type.key.type); + } else { + ovsdb_datum_to_string(&datum, &column->type, out); + } + ds_put_char(out, '\n'); + ovsdb_datum_destroy(&datum, &column->type); + + free(key_string); + } +} + +static void +list_record(const struct vsctl_table_class *table, + const struct ovsdb_idl_row *row, struct ds *out) +{ + size_t i; + + ds_put_format(out, "%-20s: "UUID_FMT"\n", "_uuid", + UUID_ARGS(&row->uuid)); + for (i = 0; i < table->class->n_columns; i++) { + const struct ovsdb_idl_column *column = &table->class->columns[i]; + struct ovsdb_datum datum; + + ovsdb_idl_txn_read(row, column, &datum); + + ds_put_format(out, "%-20s: ", column->name); + ovsdb_datum_to_string(&datum, &column->type, out); + ds_put_char(out, '\n'); + + ovsdb_datum_destroy(&datum, &column->type); + } +} + +static void +cmd_list(struct vsctl_context *ctx) +{ + const char *table_name = ctx->argv[1]; + const struct vsctl_table_class *table; + struct ds *out = &ctx->output; + int i; + + table = get_table(table_name); + if (ctx->argc > 2) { + for (i = 2; i < ctx->argc; i++) { + if (i > 2) { + ds_put_char(out, '\n'); + } + list_record(table, must_get_row(ctx, table, ctx->argv[i]), out); + } + } else { + const struct ovsdb_idl_row *row; + bool first; + + for (row = ovsdb_idl_first_row(ctx->idl, table->class), first = true; + row != NULL; + row = ovsdb_idl_next_row(row), first = false) { + if (!first) { + ds_put_char(out, '\n'); + } + list_record(table, row, out); + } + } +} + +static void +set_column(const struct vsctl_table_class *table, + const struct ovsdb_idl_row *row, const char *arg) +{ + const struct ovsdb_idl_column *column; + char *key_string, *value_string; + char *error; + + error = parse_column_key_value(arg, table, &column, &key_string, + &value_string); + die_if_error(error); + if (!value_string) { + vsctl_fatal("%s: missing value", arg); + } + + if (key_string) { + union ovsdb_atom key, value; + struct ovsdb_datum old, new; + + if (column->type.value.type == OVSDB_TYPE_VOID) { + vsctl_fatal("cannot specify key to set for non-map column %s", + column->name); + } + + die_if_error(ovsdb_atom_from_string(&key, &column->type.key, + key_string)); + die_if_error(ovsdb_atom_from_string(&value, &column->type.value, + value_string)); + + ovsdb_datum_init_empty(&new); + ovsdb_datum_add_unsafe(&new, &key, &value, &column->type); + + ovsdb_atom_destroy(&key, column->type.key.type); + ovsdb_atom_destroy(&value, column->type.value.type); + + ovsdb_idl_txn_read(row, column, &old); + ovsdb_datum_union(&old, &new, &column->type, true); + ovsdb_idl_txn_write(row, column, &old); + + ovsdb_datum_destroy(&new, &column->type); + } else { + struct ovsdb_datum datum; + + die_if_error(ovsdb_datum_from_string(&datum, &column->type, + value_string)); + ovsdb_idl_txn_write(row, column, &datum); + } + + free(key_string); + free(value_string); +} + +static void +cmd_set(struct vsctl_context *ctx) +{ + const char *table_name = ctx->argv[1]; + const char *record_id = ctx->argv[2]; + const struct vsctl_table_class *table; + const struct ovsdb_idl_row *row; + int i; + + table = get_table(table_name); + row = must_get_row(ctx, table, record_id); + for (i = 3; i < ctx->argc; i++) { + set_column(table, row, ctx->argv[i]); + } +} + +static void +cmd_add(struct vsctl_context *ctx) +{ + const char *table_name = ctx->argv[1]; + const char *record_id = ctx->argv[2]; + const char *column_name = ctx->argv[3]; + const struct vsctl_table_class *table; + const struct ovsdb_idl_column *column; + const struct ovsdb_idl_row *row; + const struct ovsdb_type *type; + struct ovsdb_datum old; + int i; + + table = get_table(table_name); + row = must_get_row(ctx, table, record_id); + die_if_error(get_column(table, column_name, &column)); + + type = &column->type; + ovsdb_idl_txn_read(row, column, &old); + for (i = 4; i < ctx->argc; i++) { + struct ovsdb_type add_type; + struct ovsdb_datum add; + + add_type = *type; + add_type.n_min = 1; + add_type.n_max = UINT_MAX; + die_if_error(ovsdb_datum_from_string(&add, &add_type, ctx->argv[i])); + ovsdb_datum_union(&old, &add, type, false); + ovsdb_datum_destroy(&add, type); + } + if (old.n > type->n_max) { + vsctl_fatal("\"add\" operation would put %u %s in column %s of " + "table %s but the maximum number is %u", + old.n, + type->value.type == OVSDB_TYPE_VOID ? "values" : "pairs", + column->name, table->class->name, type->n_max); + } + ovsdb_idl_txn_write(row, column, &old); +} + +static void +cmd_remove(struct vsctl_context *ctx) +{ + const char *table_name = ctx->argv[1]; + const char *record_id = ctx->argv[2]; + const char *column_name = ctx->argv[3]; + const struct vsctl_table_class *table; + const struct ovsdb_idl_column *column; + const struct ovsdb_idl_row *row; + const struct ovsdb_type *type; + struct ovsdb_datum old; + int i; + + table = get_table(table_name); + row = must_get_row(ctx, table, record_id); + die_if_error(get_column(table, column_name, &column)); + + type = &column->type; + ovsdb_idl_txn_read(row, column, &old); + for (i = 4; i < ctx->argc; i++) { + struct ovsdb_type rm_type; + struct ovsdb_datum rm; + char *error; + + rm_type = *type; + rm_type.n_min = 1; + rm_type.n_max = UINT_MAX; + error = ovsdb_datum_from_string(&rm, &rm_type, ctx->argv[i]); + if (error && ovsdb_type_is_map(&rm_type)) { + free(error); + rm_type.value.type = OVSDB_TYPE_VOID; + die_if_error(ovsdb_datum_from_string(&rm, &rm_type, ctx->argv[i])); + } + ovsdb_datum_subtract(&old, type, &rm, &rm_type); + ovsdb_datum_destroy(&rm, &rm_type); + } + if (old.n < type->n_min) { + vsctl_fatal("\"remove\" operation would put %u %s in column %s of " + "table %s but the minimun number is %u", + old.n, + type->value.type == OVSDB_TYPE_VOID ? "values" : "pairs", + column->name, table->class->name, type->n_min); + } + ovsdb_idl_txn_write(row, column, &old); +} + +static void +cmd_clear(struct vsctl_context *ctx) +{ + const char *table_name = ctx->argv[1]; + const char *record_id = ctx->argv[2]; + const struct vsctl_table_class *table; + const struct ovsdb_idl_row *row; + int i; + + table = get_table(table_name); + row = must_get_row(ctx, table, record_id); + for (i = 3; i < ctx->argc; i++) { + const struct ovsdb_idl_column *column; + const struct ovsdb_type *type; + struct ovsdb_datum datum; + + die_if_error(get_column(table, ctx->argv[i], &column)); + + type = &column->type; + if (type->n_min > 0) { + vsctl_fatal("\"clear\" operation cannot be applied to column %s " + "of table %s, which is not allowed to be empty", + column->name, table->class->name); + } + + ovsdb_datum_init_empty(&datum); + ovsdb_idl_txn_write(row, column, &datum); + } +} + +static void +cmd_create(struct vsctl_context *ctx) +{ + const char *table_name = ctx->argv[1]; + const struct vsctl_table_class *table; + const struct ovsdb_idl_row *row; + int i; + + table = get_table(table_name); + row = ovsdb_idl_txn_insert(ctx->txn, table->class); + for (i = 2; i < ctx->argc; i++) { + set_column(table, row, ctx->argv[i]); + } + ds_put_format(&ctx->output, UUID_FMT, UUID_ARGS(&row->uuid)); +} + +/* This function may be used as the 'postprocess' function for commands that + * insert new rows into the database. It expects that the command's 'run' + * function prints the UUID reported by ovsdb_idl_txn_insert() as the command's + * sole output. It replaces that output by the row's permanent UUID assigned + * by the database server and appends a new-line. + * + * Currently we use this only for "create", because the higher-level commands + * are supposed to be independent of the actual structure of the vswitch + * configuration. */ +static void +post_create(struct vsctl_context *ctx) +{ + const struct uuid *real; + struct uuid dummy; + + uuid_from_string(&dummy, ds_cstr(&ctx->output)); + real = ovsdb_idl_txn_get_insert_uuid(ctx->txn, &dummy); + if (real) { + ds_clear(&ctx->output); + ds_put_format(&ctx->output, UUID_FMT, UUID_ARGS(real)); + } + ds_put_char(&ctx->output, '\n'); +} + +static void +cmd_destroy(struct vsctl_context *ctx) +{ + bool must_exist = !shash_find(&ctx->options, "--if-exists"); + const char *table_name = ctx->argv[1]; + const struct vsctl_table_class *table; + int i; + + table = get_table(table_name); + for (i = 2; i < ctx->argc; i++) { + const struct ovsdb_idl_row *row; + + row = (must_exist ? must_get_row : get_row)(ctx, table, ctx->argv[i]); + if (row) { + ovsdb_idl_txn_delete(row); + } + } +} + +static struct json * +where_uuid_equals(const struct uuid *uuid) +{ + return + json_array_create_1( + json_array_create_3( + json_string_create("_uuid"), + json_string_create("=="), + json_array_create_2( + json_string_create("uuid"), + json_string_create_nocopy( + xasprintf(UUID_FMT, UUID_ARGS(uuid)))))); +} + +static void +vsctl_context_init(struct vsctl_context *ctx, struct vsctl_command *command, + struct ovsdb_idl *idl, struct ovsdb_idl_txn *txn, + const struct ovsrec_open_vswitch *ovs) +{ + ctx->argc = command->argc; + ctx->argv = command->argv; + ctx->options = command->options; + + ds_swap(&ctx->output, &command->output); + ctx->idl = idl; + ctx->txn = txn; + ctx->ovs = ovs; + +} + +static void +vsctl_context_done(struct vsctl_context *ctx, struct vsctl_command *command) +{ + ds_swap(&ctx->output, &command->output); +} + +static void +do_vsctl(const char *args, struct vsctl_command *commands, size_t n_commands, + struct ovsdb_idl *idl) +{ + struct ovsdb_idl_txn *txn; + const struct ovsrec_open_vswitch *ovs; + enum ovsdb_idl_txn_status status; + struct vsctl_command *c; + int64_t next_cfg = 0; + char *comment; + char *error; + + txn = the_idl_txn = ovsdb_idl_txn_create(idl); + if (dry_run) { + ovsdb_idl_txn_set_dry_run(txn); + } + + comment = xasprintf("ovs-vsctl: %s", args); + ovsdb_idl_txn_add_comment(txn, comment); + free(comment); + + ovs = ovsrec_open_vswitch_first(idl); + if (!ovs) { + /* XXX add verification that table is empty */ + ovs = ovsrec_open_vswitch_insert(txn); + } + + if (wait_for_reload) { + struct json *where = where_uuid_equals(&ovs->header_.uuid); + ovsdb_idl_txn_increment(txn, "Open_vSwitch", "next_cfg", where); + json_destroy(where); + } + + for (c = commands; c < &commands[n_commands]; c++) { + struct vsctl_context ctx; + + ds_init(&c->output); + vsctl_context_init(&ctx, c, idl, txn, ovs); + (c->syntax->run)(&ctx); + vsctl_context_done(&ctx, c); + } + + while ((status = ovsdb_idl_txn_commit(txn)) == TXN_INCOMPLETE) { + ovsdb_idl_run(idl); + ovsdb_idl_wait(idl); + ovsdb_idl_txn_wait(txn); + poll_block(); + } + if (wait_for_reload && status == TXN_SUCCESS) { + next_cfg = ovsdb_idl_txn_get_increment_new_value(txn); + } + for (c = commands; c < &commands[n_commands]; c++) { + if (c->syntax->postprocess) { + struct vsctl_context ctx; + + vsctl_context_init(&ctx, c, idl, txn, ovs); + (c->syntax->postprocess)(&ctx); + vsctl_context_done(&ctx, c); + } + } + error = xstrdup(ovsdb_idl_txn_get_error(txn)); + ovsdb_idl_txn_destroy(txn); + the_idl_txn = NULL; + + switch (status) { + case TXN_INCOMPLETE: + NOT_REACHED(); + + case TXN_ABORTED: + /* Should not happen--we never call ovsdb_idl_txn_abort(). */ + vsctl_fatal("transaction aborted"); + + case TXN_UNCHANGED: + case TXN_SUCCESS: + break; + + case TXN_TRY_AGAIN: + for (c = commands; c < &commands[n_commands]; c++) { + ds_destroy(&c->output); + } + free(error); + return; + + case TXN_ERROR: + vsctl_fatal("transaction error: %s", error); + + default: + NOT_REACHED(); + } + free(error); + + for (c = commands; c < &commands[n_commands]; c++) { + struct ds *ds = &c->output; + if (oneline) { + size_t j; + + ds_chomp(ds, '\n'); + for (j = 0; j < ds->length; j++) { + int c = ds->string[j]; + switch (c) { + case '\n': + fputs("\\n", stdout); + break; + + case '\\': + fputs("\\\\", stdout); + break; + + default: + putchar(c); + } + } + putchar('\n'); + } else { + fputs(ds_cstr(ds), stdout); + } + ds_destroy(&c->output); + shash_destroy(&c->options); + } + free(commands); + + if (wait_for_reload && status != TXN_UNCHANGED) { + for (;;) { + const struct ovsrec_open_vswitch *ovs; + + ovsdb_idl_run(idl); + OVSREC_OPEN_VSWITCH_FOR_EACH (ovs, idl) { + if (ovs->cur_cfg >= next_cfg) { + goto done; + } + } + ovsdb_idl_wait(idl); + poll_block(); + } + done: ; + } + ovsdb_idl_destroy(idl); + + exit(EXIT_SUCCESS); +} + +static const struct vsctl_command_syntax all_commands[] = { + /* Open vSwitch commands. */ + {"init", 0, 0, cmd_init, NULL, ""}, + + /* Bridge commands. */ + {"add-br", 1, 3, cmd_add_br, NULL, "--may-exist"}, + {"del-br", 1, 1, cmd_del_br, NULL, "--if-exists"}, + {"list-br", 0, 0, cmd_list_br, NULL, ""}, + {"br-exists", 1, 1, cmd_br_exists, NULL, ""}, + {"br-to-vlan", 1, 1, cmd_br_to_vlan, NULL, ""}, + {"br-to-parent", 1, 1, cmd_br_to_parent, NULL, ""}, + {"br-set-external-id", 2, 3, cmd_br_set_external_id, NULL, ""}, + {"br-get-external-id", 1, 2, cmd_br_get_external_id, NULL, ""}, + + /* Port commands. */ + {"list-ports", 1, 1, cmd_list_ports, NULL, ""}, + {"add-port", 2, 2, cmd_add_port, NULL, "--may-exist"}, + {"add-bond", 4, INT_MAX, cmd_add_bond, NULL, "--may-exist,--fake-iface"}, + {"del-port", 1, 2, cmd_del_port, NULL, "--if-exists"}, + {"port-to-br", 1, 1, cmd_port_to_br, NULL, ""}, + + /* Interface commands. */ + {"list-ifaces", 1, 1, cmd_list_ifaces, NULL, ""}, + {"iface-to-br", 1, 1, cmd_iface_to_br, NULL, ""}, + + /* Controller commands. */ + {"get-controller", 0, 1, cmd_get_controller, NULL, ""}, + {"del-controller", 0, 1, cmd_del_controller, NULL, ""}, + {"set-controller", 1, 2, cmd_set_controller, NULL, ""}, + {"get-fail-mode", 0, 1, cmd_get_fail_mode, NULL, ""}, + {"del-fail-mode", 0, 1, cmd_del_fail_mode, NULL, ""}, + {"set-fail-mode", 1, 2, cmd_set_fail_mode, NULL, ""}, + + /* SSL commands. */ + {"get-ssl", 0, 0, cmd_get_ssl, NULL, ""}, + {"del-ssl", 0, 0, cmd_del_ssl, NULL, ""}, + {"set-ssl", 3, 3, cmd_set_ssl, NULL, "--bootstrap"}, + + /* Parameter commands. */ + {"get", 3, INT_MAX, cmd_get, NULL, "--if-exists"}, + {"list", 1, INT_MAX, cmd_list, NULL, ""}, + {"set", 3, INT_MAX, cmd_set, NULL, ""}, + {"add", 4, INT_MAX, cmd_add, NULL, ""}, + {"remove", 4, INT_MAX, cmd_remove, NULL, ""}, + {"clear", 3, INT_MAX, cmd_clear, NULL, ""}, + {"create", 2, INT_MAX, cmd_create, post_create, ""}, + {"destroy", 1, INT_MAX, cmd_destroy, NULL, "--if-exists"}, + + {NULL, 0, 0, NULL, NULL, NULL}, +}; + diff --cc vswitchd/bridge.c index f097e183,9152062b..c2e89eb8 --- a/vswitchd/bridge.c +++ b/vswitchd/bridge.c @@@ -446,25 -418,42 +446,25 @@@ set_up_iface(const struct ovsrec_interf } static int -create_iface(const char *iface_name) -{ - return set_up_iface(iface_name, true); -} - -static int -reconfigure_iface(const char *iface_name) +reconfigure_iface(const struct ovsrec_interface *iface_cfg, struct iface *iface) { - return set_up_iface(iface_name, false); + return set_up_iface(iface_cfg, iface, false); } -static void -destroy_iface(const char *iface_name) -{ - netdev_destroy(iface_name); -} - - -/* iterate_and_prune_ifaces() callback function that opens the network device - * for 'iface', if it is not already open, and retrieves the interface's MAC - * address and carrier status. */ static bool - check_iface_netdev(struct bridge *br UNUSED, struct iface *iface, - void *aux UNUSED) -init_iface_netdev(struct bridge *br OVS_UNUSED, struct iface *iface, - void *aux OVS_UNUSED) ++check_iface_netdev(struct bridge *br OVS_UNUSED, struct iface *iface, ++ void *aux OVS_UNUSED) { - if (iface->netdev) { - return true; - } else if (!netdev_open(iface->name, NETDEV_ETH_TYPE_NONE, - &iface->netdev)) { - netdev_get_carrier(iface->netdev, &iface->enabled); - return true; - } else { - /* If the network device can't be opened, then we're not going to try - * to do anything with this interface. */ - return false; + if (!iface->netdev) { + int error = set_up_iface(iface->cfg, iface, true); + if (error) { + VLOG_WARN("could not open netdev on %s, dropping: %s", iface->name, + strerror(error)); + return false; + } } + + return true; } static bool @@@ -483,13 -473,15 +484,13 @@@ check_iface_dp_ifidx(struct bridge *br } static bool - set_iface_properties(struct bridge *br UNUSED, struct iface *iface, - void *aux UNUSED) + set_iface_properties(struct bridge *br OVS_UNUSED, struct iface *iface, + void *aux OVS_UNUSED) { - int rate, burst; - /* Set policing attributes. */ - rate = cfg_get_int(0, "port.%s.ingress.policing-rate", iface->name); - burst = cfg_get_int(0, "port.%s.ingress.policing-burst", iface->name); - netdev_set_policing(iface->netdev, rate, burst); + netdev_set_policing(iface->netdev, + iface->cfg->ingress_policing_rate, + iface->cfg->ingress_policing_burst); /* Set MAC address of internal interfaces other than the local * interface. */ @@@ -1098,8 -1053,7 +1099,8 @@@ bridge_get_local_iface(struct bridge *b /* Bridge unixctl user interface functions. */ static void -bridge_unixctl_fdb_show(struct unixctl_conn *conn, const char *args) +bridge_unixctl_fdb_show(struct unixctl_conn *conn, - const char *args, void *aux UNUSED) ++ const char *args, void *aux OVS_UNUSED) { struct ds ds = DS_EMPTY_INITIALIZER; const struct bridge *br; @@@ -1226,8 -1177,7 +1227,8 @@@ bridge_get_datapathid(const char *name /* Handle requests for a listing of all flows known by the OpenFlow * stack, including those normally hidden. */ static void -bridge_unixctl_dump_flows(struct unixctl_conn *conn, const char *args) +bridge_unixctl_dump_flows(struct unixctl_conn *conn, - const char *args, void *aux UNUSED) ++ const char *args, void *aux OVS_UNUSED) { struct bridge *br; struct ds results; @@@ -2708,8 -2699,7 +2709,8 @@@ bond_send_learning_packets(struct port /* Bonding unixctl user interface functions. */ static void -bond_unixctl_list(struct unixctl_conn *conn, const char *args OVS_UNUSED) +bond_unixctl_list(struct unixctl_conn *conn, - const char *args UNUSED, void *aux UNUSED) ++ const char *args OVS_UNUSED, void *aux OVS_UNUSED) { struct ds ds = DS_EMPTY_INITIALIZER; const struct bridge *br; @@@ -2759,8 -2749,7 +2760,8 @@@ bond_find(const char *name } static void -bond_unixctl_show(struct unixctl_conn *conn, const char *args) +bond_unixctl_show(struct unixctl_conn *conn, - const char *args, void *aux UNUSED) ++ const char *args, void *aux OVS_UNUSED) { struct ds ds = DS_EMPTY_INITIALIZER; const struct port *port; @@@ -2825,8 -2814,7 +2826,8 @@@ } static void -bond_unixctl_migrate(struct unixctl_conn *conn, const char *args_) +bond_unixctl_migrate(struct unixctl_conn *conn, const char *args_, - void *aux UNUSED) ++ void *aux OVS_UNUSED) { char *args = (char *) args_; char *save_ptr = NULL; @@@ -2882,8 -2870,7 +2883,8 @@@ } static void -bond_unixctl_set_active_slave(struct unixctl_conn *conn, const char *args_) +bond_unixctl_set_active_slave(struct unixctl_conn *conn, const char *args_, - void *aux UNUSED) ++ void *aux OVS_UNUSED) { char *args = (char *) args_; char *save_ptr = NULL; @@@ -2963,22 -2950,19 +2964,22 @@@ enable_slave(struct unixctl_conn *conn } static void -bond_unixctl_enable_slave(struct unixctl_conn *conn, const char *args) +bond_unixctl_enable_slave(struct unixctl_conn *conn, const char *args, - void *aux UNUSED) ++ void *aux OVS_UNUSED) { enable_slave(conn, args, true); } static void -bond_unixctl_disable_slave(struct unixctl_conn *conn, const char *args) +bond_unixctl_disable_slave(struct unixctl_conn *conn, const char *args, - void *aux UNUSED) ++ void *aux OVS_UNUSED) { enable_slave(conn, args, false); } static void -bond_unixctl_hash(struct unixctl_conn *conn, const char *args) +bond_unixctl_hash(struct unixctl_conn *conn, const char *args, - void *aux UNUSED) ++ void *aux OVS_UNUSED) { uint8_t mac[ETH_ADDR_LEN]; uint8_t hash; diff --cc xenserver/opt_xensource_libexec_InterfaceReconfigureVswitch.py index 6e5deb7e,7ba0d24d..59bce6f5 --- a/xenserver/opt_xensource_libexec_InterfaceReconfigureVswitch.py +++ b/xenserver/opt_xensource_libexec_InterfaceReconfigureVswitch.py @@@ -352,25 -363,23 +353,25 @@@ class DatapathVswitch(Datapath) # log("Network PIF %s not currently attached (%s)" % (rec['uuid'],pifrec['uuid'])) # continue nwrec = db().get_network_record(rec['network']) - cfgmod_argv += ['--add=bridge.%s.xs-network-uuids=%s' % (bridge, nwrec['uuid'])] - - cfgmod_argv += ["# deconfigure ipdev %s" % ipdev] - cfgmod_argv += datapath_deconfigure_ipdev(ipdev) - cfgmod_argv += ["# reconfigure ipdev %s" % ipdev] - cfgmod_argv += ['--add=bridge.%s.port=%s' % (bridge, ipdev)] - if bridge == ipdev: - cfgmod_argv += ['--add=bridge.%s.mac=%s' % (bridge, dprec['MAC'])] - else: - cfgmod_argv += ['--add=iface.%s.mac=%s' % (ipdev, dprec['MAC'])] - - if pif_is_vlan(self._pif): - cfgmod_argv += ['--add=vlan.%s.tag=%s' % (ipdev, pifrec['VLAN'])] - cfgmod_argv += ['--add=iface.%s.internal=true' % (ipdev)] - cfgmod_argv += ['--add=iface.%s.fake-bridge=true' % (ipdev)] + xs_network_uuids += [nwrec['uuid']] + + vsctl_argv += ['# configure xs-network-uuids'] + vsctl_argv += ['--', 'br-set-external-id', bridge, + 'xs-network-uuids', ';'.join(xs_network_uuids)] + + if ipdev != bridge: + vsctl_argv += ["# deconfigure ipdev %s" % ipdev] + vsctl_argv += datapath_deconfigure_ipdev(ipdev) + vsctl_argv += ["# reconfigure ipdev %s" % ipdev] + vsctl_argv += ['--', 'add-port', bridge, ipdev] + + # XXX Needs support in ovs-vsctl + #if bridge == ipdev: - # vsctl_argv += ['--add=bridge.%s.mac=%s' % (bridge, pifrec['MAC'])] ++ # vsctl_argv += ['--add=bridge.%s.mac=%s' % (bridge, dprec['MAC'])] + #else: - # vsctl_argv += ['--add=iface.%s.mac=%s' % (ipdev, pifrec['MAC'])] ++ # vsctl_argv += ['--add=iface.%s.mac=%s' % (ipdev, dprec['MAC'])] - self._cfgmod_argv = cfgmod_argv + self._vsctl_argv = vsctl_argv self._extra_ports = extra_ports def bring_down_existing(self):